# HG changeset patch # User Sebastien Jodogne # Date 1591813834 -7200 # Node ID d25f4c0fa1608baded202f42177de0edec3302be # Parent 6c6239aec462039b10882ef658702a10513c04e1 splitting code into OrthancFramework and OrthancServer diff -r 6c6239aec462 -r d25f4c0fa160 COPYING --- a/COPYING Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, 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 -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If 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 convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU 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 -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/ICachePageProvider.h --- a/Core/Cache/ICachePageProvider.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include -#include "../IDynamicObject.h" - -namespace Orthanc -{ - namespace Deprecated - { - class ICachePageProvider - { - public: - virtual ~ICachePageProvider() - { - } - - virtual IDynamicObject* Provide(const std::string& id) = 0; - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/ICacheable.h --- a/Core/Cache/ICacheable.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include - -namespace Orthanc -{ - class ICacheable : public boost::noncopyable - { - public: - virtual ~ICacheable() - { - } - - virtual size_t GetMemoryUsage() const = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/LeastRecentlyUsedIndex.h --- a/Core/Cache/LeastRecentlyUsedIndex.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,359 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include -#include -#include -#include -#include - -#include "../OrthancException.h" -#include "../Toolbox.h" - -namespace Orthanc -{ - /** - * This class implements the index of a cache with least recently - * used (LRU) recycling policy. All the items of the cache index - * can be associated with a payload. - * Reference: http://stackoverflow.com/a/2504317 - **/ - template - class LeastRecentlyUsedIndex : public boost::noncopyable - { - private: - typedef std::list< std::pair > Queue; - typedef std::map Index; - - Index index_; - Queue queue_; - - /** - * Internal method for debug builds to check whether the internal - * data structures are not corrupted. - **/ - void CheckInvariants() const; - - public: - /** - * Add a new element to the cache index, and make it the most - * recent element. - * \param id The ID of the element. - * \param payload The payload of the element. - **/ - void Add(T id, Payload payload = Payload()); - - void AddOrMakeMostRecent(T id, Payload payload = Payload()); - - /** - * When accessing an element of the cache, this method tags the - * element as the most recently used. - * \param id The most recently accessed item. - **/ - void MakeMostRecent(T id); - - void MakeMostRecent(T id, Payload updatedPayload); - - /** - * Remove an element from the cache index. - * \param id The item to remove. - **/ - Payload Invalidate(T id); - - /** - * Get the oldest element in the cache and remove it. - * \return The oldest item. - **/ - T RemoveOldest(); - - /** - * Get the oldest element in the cache, remove it and return the - * associated payload. - * \param payload Where to store the associated payload. - * \return The oldest item. - **/ - T RemoveOldest(Payload& payload); - - /** - * Check whether an element is contained in the cache. - * \param id The item. - * \return \c true iff the item is indexed by the cache. - **/ - bool Contains(T id) const - { - return index_.find(id) != index_.end(); - } - - bool Contains(T id, Payload& payload) const - { - typename Index::const_iterator it = index_.find(id); - if (it == index_.end()) - { - return false; - } - else - { - payload = it->second->second; - return true; - } - } - - /** - * Return the number of elements in the cache. - * \return The number of elements. - **/ - size_t GetSize() const - { - assert(index_.size() == queue_.size()); - return queue_.size(); - } - - /** - * Check whether the cache index is empty. - * \return \c true iff the cache is empty. - **/ - bool IsEmpty() const - { - return index_.empty(); - } - - const T& GetOldest() const; - - const Payload& GetOldestPayload() const; - - void GetAllKeys(std::vector& keys) const - { - keys.clear(); - keys.reserve(GetSize()); - for (typename Index::const_iterator it = index_.begin(); it != index_.end(); it++) - { - keys.push_back(it->first); - } - } - - }; - - - - - /****************************************************************** - ** Implementation of the template - ******************************************************************/ - - template - void LeastRecentlyUsedIndex::CheckInvariants() const - { -#ifndef NDEBUG - assert(index_.size() == queue_.size()); - - for (typename Index::const_iterator - it = index_.begin(); it != index_.end(); it++) - { - assert(it->second != queue_.end()); - assert(it->second->first == it->first); - } -#endif - } - - - template - void LeastRecentlyUsedIndex::Add(T id, Payload payload) - { - if (Contains(id)) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - queue_.push_front(std::make_pair(id, payload)); - index_[id] = queue_.begin(); - - CheckInvariants(); - } - - - template - void LeastRecentlyUsedIndex::MakeMostRecent(T id) - { - if (!Contains(id)) - { - throw OrthancException(ErrorCode_InexistentItem); - } - - typename Index::iterator it = index_.find(id); - assert(it != index_.end()); - - std::pair item = *(it->second); - - queue_.erase(it->second); - queue_.push_front(item); - index_[id] = queue_.begin(); - - CheckInvariants(); - } - - - template - void LeastRecentlyUsedIndex::AddOrMakeMostRecent(T id, Payload payload) - { - typename Index::iterator it = index_.find(id); - - if (it != index_.end()) - { - // Already existing. Make it most recent. - std::pair item = *(it->second); - item.second = payload; - queue_.erase(it->second); - queue_.push_front(item); - } - else - { - // New item - queue_.push_front(std::make_pair(id, payload)); - } - - index_[id] = queue_.begin(); - - CheckInvariants(); - } - - - template - void LeastRecentlyUsedIndex::MakeMostRecent(T id, Payload updatedPayload) - { - if (!Contains(id)) - { - throw OrthancException(ErrorCode_InexistentItem); - } - - typename Index::iterator it = index_.find(id); - assert(it != index_.end()); - - std::pair item = *(it->second); - item.second = updatedPayload; - - queue_.erase(it->second); - queue_.push_front(item); - index_[id] = queue_.begin(); - - CheckInvariants(); - } - - - template - Payload LeastRecentlyUsedIndex::Invalidate(T id) - { - if (!Contains(id)) - { - throw OrthancException(ErrorCode_InexistentItem); - } - - typename Index::iterator it = index_.find(id); - assert(it != index_.end()); - - Payload payload = it->second->second; - queue_.erase(it->second); - index_.erase(it); - - CheckInvariants(); - return payload; - } - - - template - T LeastRecentlyUsedIndex::RemoveOldest(Payload& payload) - { - if (IsEmpty()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - std::pair item = queue_.back(); - T oldest = item.first; - payload = item.second; - - queue_.pop_back(); - assert(index_.find(oldest) != index_.end()); - index_.erase(oldest); - - CheckInvariants(); - - return oldest; - } - - - template - T LeastRecentlyUsedIndex::RemoveOldest() - { - if (IsEmpty()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - std::pair item = queue_.back(); - T oldest = item.first; - - queue_.pop_back(); - assert(index_.find(oldest) != index_.end()); - index_.erase(oldest); - - CheckInvariants(); - - return oldest; - } - - - template - const T& LeastRecentlyUsedIndex::GetOldest() const - { - if (IsEmpty()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - return queue_.back().first; - } - - - template - const Payload& LeastRecentlyUsedIndex::GetOldestPayload() const - { - if (IsEmpty()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - return queue_.back().second; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/MemoryCache.cpp --- a/Core/Cache/MemoryCache.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "MemoryCache.h" - -#include "../Logging.h" - -namespace Orthanc -{ - namespace Deprecated - { - MemoryCache::Page& MemoryCache::Load(const std::string& id) - { - // Reuse the cache entry if it already exists - Page* p = NULL; - if (index_.Contains(id, p)) - { - VLOG(1) << "Reusing a cache page"; - assert(p != NULL); - index_.MakeMostRecent(id); - return *p; - } - - // The id is not in the cache yet. Make some room if the cache - // is full. - if (index_.GetSize() == cacheSize_) - { - VLOG(1) << "Dropping the oldest cache page"; - index_.RemoveOldest(p); - delete p; - } - - // Create a new cache page - std::unique_ptr result(new Page); - result->id_ = id; - result->content_.reset(provider_.Provide(id)); - - // Add the newly create page to the cache - VLOG(1) << "Registering new data in a cache page"; - p = result.release(); - index_.Add(id, p); - return *p; - } - - MemoryCache::MemoryCache(ICachePageProvider& provider, - size_t cacheSize) : - provider_(provider), - cacheSize_(cacheSize) - { - } - - void MemoryCache::Invalidate(const std::string& id) - { - Page* p = NULL; - if (index_.Contains(id, p)) - { - VLOG(1) << "Invalidating a cache page"; - assert(p != NULL); - delete p; - index_.Invalidate(id); - } - } - - MemoryCache::~MemoryCache() - { - while (!index_.IsEmpty()) - { - Page* element = NULL; - index_.RemoveOldest(element); - assert(element != NULL); - delete element; - } - } - - IDynamicObject& MemoryCache::Access(const std::string& id) - { - return *Load(id).content_; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/MemoryCache.h --- a/Core/Cache/MemoryCache.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Compatibility.h" -#include "ICachePageProvider.h" -#include "LeastRecentlyUsedIndex.h" - -#include - -namespace Orthanc -{ - namespace Deprecated - { - /** - * WARNING: This class is NOT thread-safe. - **/ - class MemoryCache - { - private: - struct Page - { - std::string id_; - std::unique_ptr content_; - }; - - ICachePageProvider& provider_; - size_t cacheSize_; - LeastRecentlyUsedIndex index_; - - Page& Load(const std::string& id); - - public: - MemoryCache(ICachePageProvider& provider, - size_t cacheSize); - - ~MemoryCache(); - - IDynamicObject& Access(const std::string& id); - - void Invalidate(const std::string& id); - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/MemoryObjectCache.cpp --- a/Core/Cache/MemoryObjectCache.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,282 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "MemoryObjectCache.h" - -#include "../Compatibility.h" - -namespace Orthanc -{ - class MemoryObjectCache::Item : public boost::noncopyable - { - private: - ICacheable* value_; - boost::posix_time::ptime time_; - - public: - explicit Item(ICacheable* value) : // Takes ownership - value_(value), - time_(boost::posix_time::second_clock::local_time()) - { - if (value == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - } - - ~Item() - { - assert(value_ != NULL); - delete value_; - } - - ICacheable& GetValue() const - { - assert(value_ != NULL); - return *value_; - } - - const boost::posix_time::ptime& GetTime() const - { - return time_; - } - }; - - - void MemoryObjectCache::Recycle(size_t targetSize) - { - // WARNING: "cacheMutex_" must be locked - while (currentSize_ > targetSize) - { - assert(!content_.IsEmpty()); - - Item* item = NULL; - content_.RemoveOldest(item); - - assert(item != NULL); - const size_t size = item->GetValue().GetMemoryUsage(); - delete item; - - assert(currentSize_ >= size); - currentSize_ -= size; - } - - // Post-condition: "currentSize_ <= targetSize" - } - - - MemoryObjectCache::MemoryObjectCache() : - currentSize_(0), - maxSize_(100 * 1024 * 1024) // 100 MB - { - } - - - MemoryObjectCache::~MemoryObjectCache() - { - Recycle(0); - assert(content_.IsEmpty()); - } - - - size_t MemoryObjectCache::GetMaximumSize() - { -#if !defined(__EMSCRIPTEN__) - boost::mutex::scoped_lock lock(cacheMutex_); -#endif - - return maxSize_; - } - - - void MemoryObjectCache::SetMaximumSize(size_t size) - { - if (size == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - -#if !defined(__EMSCRIPTEN__) - // Make sure no accessor is currently open (as its data may be - // removed if recycling is needed) - WriterLock contentLock(contentMutex_); - - // Lock the global structure of the cache - boost::mutex::scoped_lock cacheLock(cacheMutex_); -#endif - - Recycle(size); - maxSize_ = size; - } - - - void MemoryObjectCache::Acquire(const std::string& key, - ICacheable* value) - { - std::unique_ptr item(new Item(value)); - - if (value == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - else - { -#if !defined(__EMSCRIPTEN__) - // Make sure no accessor is currently open (as its data may be - // removed if recycling is needed) - WriterLock contentLock(contentMutex_); - - // Lock the global structure of the cache - boost::mutex::scoped_lock cacheLock(cacheMutex_); -#endif - - const size_t size = item->GetValue().GetMemoryUsage(); - - if (size > maxSize_) - { - // This object is too large to be stored in the cache, discard it - } - else if (content_.Contains(key)) - { - // Value already stored, don't overwrite the old value - content_.MakeMostRecent(key); - } - else - { - Recycle(maxSize_ - size); // Post-condition: currentSize_ <= maxSize_ - size - assert(currentSize_ + size <= maxSize_); - - content_.Add(key, item.release()); - currentSize_ += size; - } - } - } - - - void MemoryObjectCache::Invalidate(const std::string& key) - { -#if !defined(__EMSCRIPTEN__) - // Make sure no accessor is currently open (as it may correspond - // to the key to remove) - WriterLock contentLock(contentMutex_); - - // Lock the global structure of the cache - boost::mutex::scoped_lock cacheLock(cacheMutex_); -#endif - - Item* item = NULL; - if (content_.Contains(key, item)) - { - assert(item != NULL); - const size_t size = item->GetValue().GetMemoryUsage(); - delete item; - - content_.Invalidate(key); - - assert(currentSize_ >= size); - currentSize_ -= size; - } - } - - - MemoryObjectCache::Accessor::Accessor(MemoryObjectCache& cache, - const std::string& key, - bool unique) : - item_(NULL) - { -#if !defined(__EMSCRIPTEN__) - if (unique) - { - writerLock_ = WriterLock(cache.contentMutex_); - } - else - { - readerLock_ = ReaderLock(cache.contentMutex_); - } - - // Lock the global structure of the cache, must be *after* the - // reader/writer lock - cacheLock_ = boost::mutex::scoped_lock(cache.cacheMutex_); -#endif - - if (cache.content_.Contains(key, item_)) - { - cache.content_.MakeMostRecent(key); - } - -#if !defined(__EMSCRIPTEN__) - cacheLock_.unlock(); - - if (item_ == NULL) - { - // This item does not exist in the cache, we can release the - // reader/writer lock - if (unique) - { - writerLock_.unlock(); - } - else - { - readerLock_.unlock(); - } - } -#endif - } - - - ICacheable& MemoryObjectCache::Accessor::GetValue() const - { - if (IsValid()) - { - return item_->GetValue(); - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - const boost::posix_time::ptime& MemoryObjectCache::Accessor::GetTime() const - { - if (IsValid()) - { - return item_->GetTime(); - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/MemoryObjectCache.h --- a/Core/Cache/MemoryObjectCache.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ICacheable.h" -#include "LeastRecentlyUsedIndex.h" - -#if !defined(__EMSCRIPTEN__) -// Multithreading is not supported in WebAssembly -# include -# include -#endif - -#include - - -namespace Orthanc -{ - class MemoryObjectCache : public boost::noncopyable - { - private: - class Item; - -#if !defined(__EMSCRIPTEN__) - typedef boost::unique_lock WriterLock; - typedef boost::shared_lock ReaderLock; - - // This mutex protects modifications to the structure of the cache (monitor) - boost::mutex cacheMutex_; - - // This mutex protects modifications to the items that are stored in the cache - boost::shared_mutex contentMutex_; -#endif - - size_t currentSize_; - size_t maxSize_; - LeastRecentlyUsedIndex content_; - - void Recycle(size_t targetSize); - - public: - MemoryObjectCache(); - - ~MemoryObjectCache(); - - size_t GetMaximumSize(); - - void SetMaximumSize(size_t size); - - void Acquire(const std::string& key, - ICacheable* value); - - void Invalidate(const std::string& key); - - class Accessor : public boost::noncopyable - { - private: -#if !defined(__EMSCRIPTEN__) - ReaderLock readerLock_; - WriterLock writerLock_; - boost::mutex::scoped_lock cacheLock_; -#endif - - Item* item_; - - public: - Accessor(MemoryObjectCache& cache, - const std::string& key, - bool unique); - - bool IsValid() const - { - return item_ != NULL; - } - - ICacheable& GetValue() const; - - const boost::posix_time::ptime& GetTime() const; - }; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/MemoryStringCache.cpp --- a/Core/Cache/MemoryStringCache.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "MemoryStringCache.h" - -namespace Orthanc -{ - class MemoryStringCache::StringValue : public ICacheable - { - private: - std::string content_; - - public: - StringValue(const std::string& content) : - content_(content) - { - } - - const std::string& GetContent() const - { - return content_; - } - - virtual size_t GetMemoryUsage() const - { - return content_.size(); - } - }; - - - void MemoryStringCache::Add(const std::string& key, - const std::string& value) - { - cache_.Acquire(key, new StringValue(value)); - } - - - bool MemoryStringCache::Fetch(std::string& value, - const std::string& key) - { - MemoryObjectCache::Accessor reader(cache_, key, false /* multiple readers are allowed */); - - if (reader.IsValid()) - { - value = dynamic_cast(reader.GetValue()).GetContent(); - return true; - } - else - { - return false; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/MemoryStringCache.h --- a/Core/Cache/MemoryStringCache.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "MemoryObjectCache.h" - -namespace Orthanc -{ - /** - * Facade object around "MemoryObjectCache" that caches a dictionary - * of strings, using the "fetch/add" paradigm of memcached. - **/ - class MemoryStringCache : public boost::noncopyable - { - private: - class StringValue; - - MemoryObjectCache cache_; - - public: - size_t GetMaximumSize() - { - return cache_.GetMaximumSize(); - } - - void SetMaximumSize(size_t size) - { - cache_.SetMaximumSize(size); - } - - void Add(const std::string& key, - const std::string& value); - - void Invalidate(const std::string& key) - { - cache_.Invalidate(key); - } - - bool Fetch(std::string& value, - const std::string& key); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/SharedArchive.cpp --- a/Core/Cache/SharedArchive.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "SharedArchive.h" - -#include "../Toolbox.h" - - -namespace Orthanc -{ - void SharedArchive::RemoveInternal(const std::string& id) - { - Archive::iterator it = archive_.find(id); - - if (it != archive_.end()) - { - delete it->second; - archive_.erase(it); - - lru_.Invalidate(id); - } - } - - - SharedArchive::Accessor::Accessor(SharedArchive& that, - const std::string& id) : - lock_(that.mutex_) - { - Archive::iterator it = that.archive_.find(id); - - if (it == that.archive_.end()) - { - item_ = NULL; - } - else - { - that.lru_.MakeMostRecent(id); - item_ = it->second; - } - } - - - IDynamicObject& SharedArchive::Accessor::GetItem() const - { - if (item_ == NULL) - { - // "IsValid()" should have been called - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return *item_; - } - } - - - SharedArchive::SharedArchive(size_t maxSize) : - maxSize_(maxSize) - { - if (maxSize == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - SharedArchive::~SharedArchive() - { - for (Archive::iterator it = archive_.begin(); - it != archive_.end(); ++it) - { - delete it->second; - } - } - - - std::string SharedArchive::Add(IDynamicObject* obj) - { - boost::mutex::scoped_lock lock(mutex_); - - if (archive_.size() == maxSize_) - { - // The quota has been reached, remove the oldest element - RemoveInternal(lru_.GetOldest()); - } - - std::string id = Toolbox::GenerateUuid(); - RemoveInternal(id); // Should never be useful because of UUID - - archive_[id] = obj; - lru_.Add(id); - - return id; - } - - - void SharedArchive::Remove(const std::string& id) - { - boost::mutex::scoped_lock lock(mutex_); - RemoveInternal(id); - } - - - void SharedArchive::List(std::list& items) - { - items.clear(); - - { - boost::mutex::scoped_lock lock(mutex_); - - for (Archive::const_iterator it = archive_.begin(); - it != archive_.end(); ++it) - { - items.push_back(it->first); - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Cache/SharedArchive.h --- a/Core/Cache/SharedArchive.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The class SharedArchive cannot be used in sandboxed environments -#endif - -#include "LeastRecentlyUsedIndex.h" -#include "../IDynamicObject.h" - -#include -#include - -namespace Orthanc -{ - class SharedArchive : public boost::noncopyable - { - private: - typedef std::map Archive; - - size_t maxSize_; - boost::mutex mutex_; - Archive archive_; - LeastRecentlyUsedIndex lru_; - - void RemoveInternal(const std::string& id); - - public: - class Accessor : public boost::noncopyable - { - private: - boost::mutex::scoped_lock lock_; - IDynamicObject* item_; - - public: - Accessor(SharedArchive& that, - const std::string& id); - - bool IsValid() const - { - return item_ != NULL; - } - - IDynamicObject& GetItem() const; - }; - - - SharedArchive(size_t maxSize); - - ~SharedArchive(); - - std::string Add(IDynamicObject* obj); // Takes the ownership - - void Remove(const std::string& id); - - void List(std::list& items); - }; -} - - diff -r 6c6239aec462 -r d25f4c0fa160 Core/ChunkedBuffer.cpp --- a/Core/ChunkedBuffer.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "ChunkedBuffer.h" - -#include -#include - - -namespace Orthanc -{ - void ChunkedBuffer::Clear() - { - numBytes_ = 0; - - for (Chunks::iterator it = chunks_.begin(); - it != chunks_.end(); ++it) - { - delete *it; - } - } - - - void ChunkedBuffer::AddChunk(const void* chunkData, - size_t chunkSize) - { - if (chunkSize == 0) - { - return; - } - else - { - assert(chunkData != NULL); - chunks_.push_back(new std::string(reinterpret_cast(chunkData), chunkSize)); - numBytes_ += chunkSize; - } - } - - - void ChunkedBuffer::AddChunk(const std::string& chunk) - { - if (chunk.size() > 0) - { - AddChunk(&chunk[0], chunk.size()); - } - } - - - void ChunkedBuffer::AddChunkDestructive(std::string& chunk) - { - size_t chunkSize = chunk.size(); - - if (chunkSize > 0) - { - chunks_.push_back(new std::string); - chunks_.back()->swap(chunk); - numBytes_ += chunkSize; - } - } - - - void ChunkedBuffer::Flatten(std::string& result) - { - result.resize(numBytes_); - - size_t pos = 0; - for (Chunks::iterator it = chunks_.begin(); - it != chunks_.end(); ++it) - { - assert(*it != NULL); - - size_t s = (*it)->size(); - if (s != 0) - { - memcpy(&result[pos], (*it)->c_str(), s); - pos += s; - } - - delete *it; - } - - chunks_.clear(); - numBytes_ = 0; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/ChunkedBuffer.h --- a/Core/ChunkedBuffer.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "OrthancFramework.h" - -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC ChunkedBuffer : public boost::noncopyable - { - private: - typedef std::list Chunks; - size_t numBytes_; - Chunks chunks_; - - void Clear(); - - public: - ChunkedBuffer() : numBytes_(0) - { - } - - ~ChunkedBuffer() - { - Clear(); - } - - size_t GetNumBytes() const - { - return numBytes_; - } - - void AddChunk(const void* chunkData, - size_t chunkSize); - - void AddChunk(const std::string& chunk); - - // The source content will be emptied - void AddChunkDestructive(std::string& chunk); - - void Flatten(std::string& result); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compatibility.h --- a/Core/Compatibility.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -//#define Orthanc_Compatibility_h_STR2(x) #x -//#define Orthanc_Compatibility_h_STR1(x) Orthanc_Compatibility_h_STR2(x) - -//#pragma message("__cplusplus = " Orthanc_Compatibility_h_STR1(__cplusplus)) - -#if (defined _MSC_VER) -//# pragma message("_MSC_VER = " Orthanc_Compatibility_h_STR1(_MSC_VER)) -//# pragma message("_MSVC_LANG = " Orthanc_Compatibility_h_STR1(_MSVC_LANG)) -// The __cplusplus macro cannot be used in Visual C++ < 1914 (VC++ 15.7) -// However, even in recent versions, __cplusplus will only be correct (that is, -// correctly defines the supported C++ version) if a special flag is passed to -// the compiler ("/Zc:__cplusplus") -// To make this header more robust, we use the _MSVC_LANG equivalent macro. - -// please note that not all C++11 features are supported when _MSC_VER == 1600 -// (or higher). This header file can be made for fine-grained, if required, -// based on specific _MSC_VER values - -# if _MSC_VER >= 1600 -# define ORTHANC_Cxx03_DETECTED 0 -# else -# define ORTHANC_Cxx03_DETECTED 1 -# endif - -#else -// of _MSC_VER is not defined, we assume __cplusplus is correctly defined -// if __cplusplus is not defined (very old compilers??), then the following -// test will compare 0 < 201103L and will be true --> safe. -# if __cplusplus < 201103L -# define ORTHANC_Cxx03_DETECTED 1 -# else -# define ORTHANC_Cxx03_DETECTED 0 -# endif -#endif - -#if ORTHANC_Cxx03_DETECTED == 1 -//#pragma message("C++ 11 support is not present.") - -/** - * "std::unique_ptr" was introduced in C++11, and "std::auto_ptr" was - * removed in C++17. We emulate "std::auto_ptr" using boost: "The - * smart pointer unique_ptr [is] a drop-in replacement for - * std::unique_ptr, usable also from C++03 compilers." This is only - * available if Boost >= 1.57.0 (from November 2014). - * https://www.boost.org/doc/libs/1_57_0/doc/html/move/reference.html#header.boost.move.unique_ptr_hpp - **/ - -#include - -namespace std -{ - template - class unique_ptr : public boost::movelib::unique_ptr - { - public: - explicit unique_ptr() : - boost::movelib::unique_ptr() - { - } - - explicit unique_ptr(T* p) : - boost::movelib::unique_ptr(p) - { - } - }; -} -#else -//# pragma message("C++ 11 support is present.") -# include -#endif diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/DeflateBaseCompressor.cpp --- a/Core/Compression/DeflateBaseCompressor.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DeflateBaseCompressor.h" - -#include "../OrthancException.h" -#include "../Logging.h" - -#include - -namespace Orthanc -{ - void DeflateBaseCompressor::SetCompressionLevel(uint8_t level) - { - if (level >= 10) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Zlib compression level must be between 0 (no compression) and 9 (highest compression)"); - } - - compressionLevel_ = level; - } - - - uint64_t DeflateBaseCompressor::ReadUncompressedSizePrefix(const void* compressed, - size_t compressedSize) - { - if (compressedSize == 0) - { - return 0; - } - - if (compressedSize < sizeof(uint64_t)) - { - throw OrthancException(ErrorCode_CorruptedFile, "The compressed buffer is ill-formed"); - } - - uint64_t size; - memcpy(&size, compressed, sizeof(uint64_t)); - - return size; - } - -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/DeflateBaseCompressor.h --- a/Core/Compression/DeflateBaseCompressor.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IBufferCompressor.h" - -#if !defined(ORTHANC_ENABLE_ZLIB) -# error The macro ORTHANC_ENABLE_ZLIB must be defined -#endif - -#if ORTHANC_ENABLE_ZLIB != 1 -# error ZLIB support must be enabled to include this file -#endif - - -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC DeflateBaseCompressor : public IBufferCompressor - { - private: - uint8_t compressionLevel_; - bool prefixWithUncompressedSize_; - - protected: - uint64_t ReadUncompressedSizePrefix(const void* compressed, - size_t compressedSize); - - public: - DeflateBaseCompressor() : - compressionLevel_(6), - prefixWithUncompressedSize_(false) - { - } - - void SetCompressionLevel(uint8_t level); - - void SetPrefixWithUncompressedSize(bool prefix) - { - prefixWithUncompressedSize_ = prefix; - } - - bool HasPrefixWithUncompressedSize() const - { - return prefixWithUncompressedSize_; - } - - uint8_t GetCompressionLevel() const - { - return compressionLevel_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/GzipCompressor.cpp --- a/Core/Compression/GzipCompressor.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,280 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "GzipCompressor.h" - -#include -#include -#include - -#include "../OrthancException.h" -#include "../Logging.h" - -namespace Orthanc -{ - uint64_t GzipCompressor::GuessUncompressedSize(const void* compressed, - size_t compressedSize) - { - /** - * "Is there a way to find out the size of the original file which - * is inside a GZIP file? [...] There is no truly reliable way, - * other than gunzipping the stream. You do not need to save the - * result of the decompression, so you can determine the size by - * simply reading and decoding the entire file without taking up - * space with the decompressed result. - * - * There is an unreliable way to determine the uncompressed size, - * which is to look at the last four bytes of the gzip file, which - * is the uncompressed length of that entry modulo 232 in little - * endian order. - * - * It is unreliable because a) the uncompressed data may be longer - * than 2^32 bytes, and b) the gzip file may consist of multiple - * gzip streams, in which case you would find the length of only - * the last of those streams. - * - * If you are in control of the source of the gzip files, you know - * that they consist of single gzip streams, and you know that - * they are less than 2^32 bytes uncompressed, then and only then - * can you use those last four bytes with confidence." - * - * http://stackoverflow.com/a/9727599/881731 - **/ - - if (compressedSize < 4) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - const uint8_t* p = reinterpret_cast(compressed) + compressedSize - 4; - - return ((static_cast(p[0]) << 0) + - (static_cast(p[1]) << 8) + - (static_cast(p[2]) << 16) + - (static_cast(p[3]) << 24)); - } - - - - void GzipCompressor::Compress(std::string& compressed, - const void* uncompressed, - size_t uncompressedSize) - { - uLongf compressedSize = compressBound(static_cast(uncompressedSize)) - + 1024 /* security margin */; - - if (compressedSize == 0) - { - compressedSize = 1; - } - - uint8_t* target; - if (HasPrefixWithUncompressedSize()) - { - compressed.resize(compressedSize + sizeof(uint64_t)); - target = reinterpret_cast(&compressed[0]) + sizeof(uint64_t); - } - else - { - compressed.resize(compressedSize); - target = reinterpret_cast(&compressed[0]); - } - - z_stream stream; - memset(&stream, 0, sizeof(stream)); - - stream.next_in = const_cast(reinterpret_cast(uncompressed)); - stream.next_out = reinterpret_cast(target); - - stream.avail_in = static_cast(uncompressedSize); - stream.avail_out = static_cast(compressedSize); - - // Ensure no overflow (if the buffer is too large for the current archicture) - if (static_cast(stream.avail_in) != uncompressedSize || - static_cast(stream.avail_out) != compressedSize) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - // Initialize the compression engine - int error = deflateInit2(&stream, - GetCompressionLevel(), - Z_DEFLATED, - MAX_WBITS + 16, // ask for gzip output - 8, // default memory level - Z_DEFAULT_STRATEGY); - - if (error != Z_OK) - { - // Cannot initialize zlib - compressed.clear(); - throw OrthancException(ErrorCode_InternalError); - } - - // Compress the input buffer - error = deflate(&stream, Z_FINISH); - - if (error != Z_STREAM_END) - { - deflateEnd(&stream); - compressed.clear(); - - switch (error) - { - case Z_MEM_ERROR: - throw OrthancException(ErrorCode_NotEnoughMemory); - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - size_t size = stream.total_out; - - if (deflateEnd(&stream) != Z_OK) - { - throw OrthancException(ErrorCode_InternalError); - } - - // The compression was successful - if (HasPrefixWithUncompressedSize()) - { - uint64_t s = static_cast(uncompressedSize); - memcpy(&compressed[0], &s, sizeof(uint64_t)); - compressed.resize(size + sizeof(uint64_t)); - } - else - { - compressed.resize(size); - } - } - - - void GzipCompressor::Uncompress(std::string& uncompressed, - const void* compressed, - size_t compressedSize) - { - uint64_t uncompressedSize; - const uint8_t* source = reinterpret_cast(compressed); - - if (HasPrefixWithUncompressedSize()) - { - uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize); - source += sizeof(uint64_t); - compressedSize -= sizeof(uint64_t); - } - else - { - uncompressedSize = GuessUncompressedSize(compressed, compressedSize); - } - - try - { - uncompressed.resize(static_cast(uncompressedSize)); - } - catch (...) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - z_stream stream; - memset(&stream, 0, sizeof(stream)); - - char dummy = '\0'; // zlib does not like NULL output buffers (even if the uncompressed data is empty) - stream.next_in = const_cast(source); - stream.next_out = reinterpret_cast(uncompressedSize == 0 ? &dummy : &uncompressed[0]); - - stream.avail_in = static_cast(compressedSize); - stream.avail_out = static_cast(uncompressedSize); - - // Ensure no overflow (if the buffer is too large for the current archicture) - if (static_cast(stream.avail_in) != compressedSize || - static_cast(stream.avail_out) != uncompressedSize) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - // Initialize the compression engine - int error = inflateInit2(&stream, - MAX_WBITS + 16); // this is a gzip input - - if (error != Z_OK) - { - // Cannot initialize zlib - uncompressed.clear(); - throw OrthancException(ErrorCode_InternalError); - } - - // Uncompress the input buffer - error = inflate(&stream, Z_FINISH); - - if (error != Z_STREAM_END) - { - inflateEnd(&stream); - uncompressed.clear(); - - switch (error) - { - case Z_MEM_ERROR: - throw OrthancException(ErrorCode_NotEnoughMemory); - - case Z_BUF_ERROR: - case Z_NEED_DICT: - throw OrthancException(ErrorCode_BadFileFormat); - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - size_t size = stream.total_out; - - if (inflateEnd(&stream) != Z_OK) - { - uncompressed.clear(); - throw OrthancException(ErrorCode_InternalError); - } - - if (size != uncompressedSize) - { - uncompressed.clear(); - - // The uncompressed size was not that properly guess, presumably - // because of a file size over 4GB. Should fallback to - // stream-based decompression. - throw OrthancException(ErrorCode_NotImplemented, - "The uncompressed size of a gzip-encoded buffer was not properly guessed"); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/GzipCompressor.h --- a/Core/Compression/GzipCompressor.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DeflateBaseCompressor.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC GzipCompressor : public DeflateBaseCompressor - { - private: - uint64_t GuessUncompressedSize(const void* compressed, - size_t compressedSize); - - public: - GzipCompressor() - { - SetPrefixWithUncompressedSize(false); - } - - virtual void Compress(std::string& compressed, - const void* uncompressed, - size_t uncompressedSize); - - virtual void Uncompress(std::string& uncompressed, - const void* compressed, - size_t compressedSize); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/HierarchicalZipWriter.cpp --- a/Core/Compression/HierarchicalZipWriter.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "HierarchicalZipWriter.h" - -#include "../Toolbox.h" -#include "../OrthancException.h" - -#include - -namespace Orthanc -{ - std::string HierarchicalZipWriter::Index::KeepAlphanumeric(const std::string& source) - { - std::string result; - - bool lastSpace = false; - - result.reserve(source.size()); - for (size_t i = 0; i < source.size(); i++) - { - char c = source[i]; - if (c == '^') - c = ' '; - - if (c <= 127 && - c >= 0) - { - if (isspace(c)) - { - if (!lastSpace) - { - lastSpace = true; - result.push_back(' '); - } - } - else if (isalnum(c) || - c == '.' || - c == '_') - { - result.push_back(c); - lastSpace = false; - } - } - } - - return Toolbox::StripSpaces(result); - } - - std::string HierarchicalZipWriter::Index::GetCurrentDirectoryPath() const - { - std::string result; - - Stack::const_iterator it = stack_.begin(); - ++it; // Skip the root node (to avoid absolute paths) - - while (it != stack_.end()) - { - result += (*it)->name_ + "/"; - ++it; - } - - return result; - } - - std::string HierarchicalZipWriter::Index::EnsureUniqueFilename(const char* filename) - { - std::string standardized = KeepAlphanumeric(filename); - - Directory& d = *stack_.back(); - Directory::Content::iterator it = d.content_.find(standardized); - - if (it == d.content_.end()) - { - d.content_[standardized] = 1; - return standardized; - } - else - { - it->second++; - return standardized + "-" + boost::lexical_cast(it->second); - } - } - - HierarchicalZipWriter::Index::Index() - { - stack_.push_back(new Directory); - } - - HierarchicalZipWriter::Index::~Index() - { - for (Stack::iterator it = stack_.begin(); it != stack_.end(); ++it) - { - delete *it; - } - } - - std::string HierarchicalZipWriter::Index::OpenFile(const char* name) - { - return GetCurrentDirectoryPath() + EnsureUniqueFilename(name); - } - - void HierarchicalZipWriter::Index::OpenDirectory(const char* name) - { - std::string d = EnsureUniqueFilename(name); - - // Push the new directory onto the stack - stack_.push_back(new Directory); - stack_.back()->name_ = d; - } - - void HierarchicalZipWriter::Index::CloseDirectory() - { - if (IsRoot()) - { - // Cannot close the root node - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - delete stack_.back(); - stack_.pop_back(); - } - - - HierarchicalZipWriter::HierarchicalZipWriter(const char* path) - { - writer_.SetOutputPath(path); - writer_.Open(); - } - - HierarchicalZipWriter::~HierarchicalZipWriter() - { - writer_.Close(); - } - - void HierarchicalZipWriter::OpenFile(const char* name) - { - std::string p = indexer_.OpenFile(name); - writer_.OpenFile(p.c_str()); - } - - void HierarchicalZipWriter::OpenDirectory(const char* name) - { - indexer_.OpenDirectory(name); - } - - void HierarchicalZipWriter::CloseDirectory() - { - indexer_.CloseDirectory(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/HierarchicalZipWriter.h --- a/Core/Compression/HierarchicalZipWriter.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,153 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ZipWriter.h" - -#include -#include -#include - -#if ORTHANC_BUILD_UNIT_TESTS == 1 -# include -#endif - -namespace Orthanc -{ - class ORTHANC_PUBLIC HierarchicalZipWriter : public boost::noncopyable - { -#if ORTHANC_BUILD_UNIT_TESTS == 1 - FRIEND_TEST(HierarchicalZipWriter, Index); - FRIEND_TEST(HierarchicalZipWriter, Filenames); -#endif - - private: - class ORTHANC_PUBLIC Index - { - private: - struct Directory - { - typedef std::map Content; - - std::string name_; - Content content_; - }; - - typedef std::list Stack; - - Stack stack_; - - std::string EnsureUniqueFilename(const char* filename); - - public: - Index(); - - ~Index(); - - bool IsRoot() const - { - return stack_.size() == 1; - } - - std::string OpenFile(const char* name); - - void OpenDirectory(const char* name); - - void CloseDirectory(); - - std::string GetCurrentDirectoryPath() const; - - static std::string KeepAlphanumeric(const std::string& source); - }; - - Index indexer_; - ZipWriter writer_; - - public: - HierarchicalZipWriter(const char* path); - - ~HierarchicalZipWriter(); - - void SetZip64(bool isZip64) - { - writer_.SetZip64(isZip64); - } - - bool IsZip64() const - { - return writer_.IsZip64(); - } - - void SetCompressionLevel(uint8_t level) - { - writer_.SetCompressionLevel(level); - } - - uint8_t GetCompressionLevel() const - { - return writer_.GetCompressionLevel(); - } - - void SetAppendToExisting(bool append) - { - writer_.SetAppendToExisting(append); - } - - bool IsAppendToExisting() const - { - return writer_.IsAppendToExisting(); - } - - void OpenFile(const char* name); - - void OpenDirectory(const char* name); - - void CloseDirectory(); - - std::string GetCurrentDirectoryPath() const - { - return indexer_.GetCurrentDirectoryPath(); - } - - void Write(const void* data, size_t length) - { - writer_.Write(data, length); - } - - void Write(const std::string& data) - { - writer_.Write(data); - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/IBufferCompressor.h --- a/Core/Compression/IBufferCompressor.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../OrthancFramework.h" - -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC IBufferCompressor : public boost::noncopyable - { - public: - virtual ~IBufferCompressor() - { - } - - virtual void Compress(std::string& compressed, - const void* uncompressed, - size_t uncompressedSize) = 0; - - virtual void Uncompress(std::string& uncompressed, - const void* compressed, - size_t compressedSize) = 0; - - static void Compress(std::string& compressed, - IBufferCompressor& compressor, - const std::string& uncompressed) - { - compressor.Compress(compressed, - uncompressed.size() == 0 ? NULL : uncompressed.c_str(), - uncompressed.size()); - } - - static void Uncompress(std::string& uncompressed, - IBufferCompressor& compressor, - const std::string& compressed) - { - compressor.Uncompress(uncompressed, - compressed.size() == 0 ? NULL : compressed.c_str(), - compressed.size()); - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/ZipWriter.cpp --- a/Core/Compression/ZipWriter.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,262 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include "ZipWriter.h" - -#include -#include -#include - -#include "../../Resources/ThirdParty/minizip/zip.h" -#include "../OrthancException.h" -#include "../Logging.h" - - -static void PrepareFileInfo(zip_fileinfo& zfi) -{ - memset(&zfi, 0, sizeof(zfi)); - - using namespace boost::posix_time; - ptime now = second_clock::local_time(); - - boost::gregorian::date today = now.date(); - ptime midnight(today); - - time_duration sinceMidnight = now - midnight; - zfi.tmz_date.tm_sec = static_cast(sinceMidnight.seconds()); // seconds after the minute - [0,59] - zfi.tmz_date.tm_min = static_cast(sinceMidnight.minutes()); // minutes after the hour - [0,59] - zfi.tmz_date.tm_hour = static_cast(sinceMidnight.hours()); // hours since midnight - [0,23] - - // http://www.boost.org/doc/libs/1_35_0/doc/html/boost/gregorian/greg_day.html - zfi.tmz_date.tm_mday = today.day(); // day of the month - [1,31] - - // http://www.boost.org/doc/libs/1_35_0/doc/html/boost/gregorian/greg_month.html - zfi.tmz_date.tm_mon = today.month() - 1; // months since January - [0,11] - - // http://www.boost.org/doc/libs/1_35_0/doc/html/boost/gregorian/greg_year.html - zfi.tmz_date.tm_year = today.year(); // years - [1980..2044] -} - - - -namespace Orthanc -{ - struct ZipWriter::PImpl - { - zipFile file_; - - PImpl() : file_(NULL) - { - } - }; - - ZipWriter::ZipWriter() : - pimpl_(new PImpl), - isZip64_(false), - hasFileInZip_(false), - append_(false), - compressionLevel_(6) - { - } - - ZipWriter::~ZipWriter() - { - Close(); - } - - void ZipWriter::Close() - { - if (IsOpen()) - { - zipClose(pimpl_->file_, "Created by Orthanc"); - pimpl_->file_ = NULL; - hasFileInZip_ = false; - } - } - - bool ZipWriter::IsOpen() const - { - return pimpl_->file_ != NULL; - } - - void ZipWriter::Open() - { - if (IsOpen()) - { - return; - } - - if (path_.size() == 0) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "Please call SetOutputPath() before creating the file"); - } - - hasFileInZip_ = false; - - int mode = APPEND_STATUS_CREATE; - if (append_ && - boost::filesystem::exists(path_)) - { - mode = APPEND_STATUS_ADDINZIP; - } - - if (isZip64_) - { - pimpl_->file_ = zipOpen64(path_.c_str(), mode); - } - else - { - pimpl_->file_ = zipOpen(path_.c_str(), mode); - } - - if (!pimpl_->file_) - { - throw OrthancException(ErrorCode_CannotWriteFile, - "Cannot create new ZIP archive: " + path_); - } - } - - void ZipWriter::SetOutputPath(const char* path) - { - Close(); - path_ = path; - } - - void ZipWriter::SetZip64(bool isZip64) - { - Close(); - isZip64_ = isZip64; - } - - void ZipWriter::SetCompressionLevel(uint8_t level) - { - if (level >= 10) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "ZIP compression level must be between 0 (no compression) " - "and 9 (highest compression)"); - } - - Close(); - compressionLevel_ = level; - } - - void ZipWriter::OpenFile(const char* path) - { - Open(); - - zip_fileinfo zfi; - PrepareFileInfo(zfi); - - int result; - - if (isZip64_) - { - result = zipOpenNewFileInZip64(pimpl_->file_, path, - &zfi, - NULL, 0, - NULL, 0, - "", // Comment - Z_DEFLATED, - compressionLevel_, 1); - } - else - { - result = zipOpenNewFileInZip(pimpl_->file_, path, - &zfi, - NULL, 0, - NULL, 0, - "", // Comment - Z_DEFLATED, - compressionLevel_); - } - - if (result != 0) - { - throw OrthancException(ErrorCode_CannotWriteFile, - "Cannot add new file inside ZIP archive: " + std::string(path)); - } - - hasFileInZip_ = true; - } - - - void ZipWriter::Write(const std::string& data) - { - if (data.size()) - { - Write(&data[0], data.size()); - } - } - - - void ZipWriter::Write(const void* data, size_t length) - { - if (!hasFileInZip_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, "Call first OpenFile()"); - } - - const size_t maxBytesInAStep = std::numeric_limits::max(); - - const char* p = reinterpret_cast(data); - - while (length > 0) - { - int bytes = static_cast(length <= maxBytesInAStep ? length : maxBytesInAStep); - - if (zipWriteInFileInZip(pimpl_->file_, p, bytes)) - { - throw OrthancException(ErrorCode_CannotWriteFile, - "Cannot write data to ZIP archive: " + path_); - } - - p += bytes; - length -= bytes; - } - } - - - void ZipWriter::SetAppendToExisting(bool append) - { - Close(); - append_ = append; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/ZipWriter.h --- a/Core/Compression/ZipWriter.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,111 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../OrthancFramework.h" - -#if !defined(ORTHANC_ENABLE_ZLIB) -# error The macro ORTHANC_ENABLE_ZLIB must be defined -#endif - -#if ORTHANC_ENABLE_ZLIB != 1 -# error ZLIB support must be enabled to include this file -#endif - - -#include -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC ZipWriter : public boost::noncopyable - { - private: - struct PImpl; - boost::shared_ptr pimpl_; - - bool isZip64_; - bool hasFileInZip_; - bool append_; - uint8_t compressionLevel_; - std::string path_; - - public: - ZipWriter(); - - ~ZipWriter(); - - void SetZip64(bool isZip64); - - bool IsZip64() const - { - return isZip64_; - } - - void SetCompressionLevel(uint8_t level); - - uint8_t GetCompressionLevel() const - { - return compressionLevel_; - } - - void SetAppendToExisting(bool append); - - bool IsAppendToExisting() const - { - return append_; - } - - void Open(); - - void Close(); - - bool IsOpen() const; - - void SetOutputPath(const char* path); - - const std::string& GetOutputPath() const - { - return path_; - } - - void OpenFile(const char* path); - - void Write(const void* data, size_t length); - - void Write(const std::string& data); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/ZlibCompressor.cpp --- a/Core/Compression/ZlibCompressor.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "ZlibCompressor.h" - -#include "../OrthancException.h" -#include "../Logging.h" - -#include -#include -#include - -namespace Orthanc -{ - void ZlibCompressor::Compress(std::string& compressed, - const void* uncompressed, - size_t uncompressedSize) - { - if (uncompressedSize == 0) - { - compressed.clear(); - return; - } - - uLongf compressedSize = compressBound(static_cast(uncompressedSize)) - + 1024 /* security margin */; - if (compressedSize == 0) - { - compressedSize = 1; - } - - uint8_t* target; - if (HasPrefixWithUncompressedSize()) - { - compressed.resize(compressedSize + sizeof(uint64_t)); - target = reinterpret_cast(&compressed[0]) + sizeof(uint64_t); - } - else - { - compressed.resize(compressedSize); - target = reinterpret_cast(&compressed[0]); - } - - int error = compress2(target, - &compressedSize, - const_cast(static_cast(uncompressed)), - static_cast(uncompressedSize), - GetCompressionLevel()); - - if (error != Z_OK) - { - compressed.clear(); - - switch (error) - { - case Z_MEM_ERROR: - throw OrthancException(ErrorCode_NotEnoughMemory); - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - // The compression was successful - if (HasPrefixWithUncompressedSize()) - { - uint64_t s = static_cast(uncompressedSize); - memcpy(&compressed[0], &s, sizeof(uint64_t)); - compressed.resize(compressedSize + sizeof(uint64_t)); - } - else - { - compressed.resize(compressedSize); - } - } - - - void ZlibCompressor::Uncompress(std::string& uncompressed, - const void* compressed, - size_t compressedSize) - { - if (compressedSize == 0) - { - uncompressed.clear(); - return; - } - - if (!HasPrefixWithUncompressedSize()) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot guess the uncompressed size of a zlib-encoded buffer"); - } - - uint64_t uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize); - - try - { - uncompressed.resize(static_cast(uncompressedSize)); - } - catch (...) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - uLongf tmp = static_cast(uncompressedSize); - int error = uncompress - (reinterpret_cast(&uncompressed[0]), - &tmp, - reinterpret_cast(compressed) + sizeof(uint64_t), - static_cast(compressedSize - sizeof(uint64_t))); - - if (error != Z_OK) - { - uncompressed.clear(); - - switch (error) - { - case Z_DATA_ERROR: - throw OrthancException(ErrorCode_CorruptedFile); - - case Z_MEM_ERROR: - throw OrthancException(ErrorCode_NotEnoughMemory); - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Compression/ZlibCompressor.h --- a/Core/Compression/ZlibCompressor.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DeflateBaseCompressor.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC ZlibCompressor : public DeflateBaseCompressor - { - public: - ZlibCompressor() - { - SetPrefixWithUncompressedSize(true); - } - - virtual void Compress(std::string& compressed, - const void* uncompressed, - size_t uncompressedSize); - - virtual void Uncompress(std::string& uncompressed, - const void* compressed, - size_t compressedSize); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomArray.cpp --- a/Core/DicomFormat/DicomArray.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomArray.h" - -#include - -namespace Orthanc -{ - DicomArray::DicomArray(const DicomMap& map) - { - elements_.reserve(map.content_.size()); - - for (DicomMap::Content::const_iterator it = - map.content_.begin(); it != map.content_.end(); ++it) - { - elements_.push_back(new DicomElement(it->first, *it->second)); - } - } - - - DicomArray::~DicomArray() - { - for (size_t i = 0; i < elements_.size(); i++) - { - delete elements_[i]; - } - } - - - void DicomArray::Print(FILE* fp) const - { - for (size_t i = 0; i < elements_.size(); i++) - { - DicomTag t = elements_[i]->GetTag(); - const DicomValue& v = elements_[i]->GetValue(); - std::string s = v.IsNull() ? "(null)" : v.GetContent(); - printf("0x%04x 0x%04x [%s]\n", t.GetGroup(), t.GetElement(), s.c_str()); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomArray.h --- a/Core/DicomFormat/DicomArray.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DicomElement.h" -#include "DicomMap.h" - -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC DicomArray : public boost::noncopyable - { - private: - typedef std::vector Elements; - - Elements elements_; - - public: - explicit DicomArray(const DicomMap& map); - - ~DicomArray(); - - size_t GetSize() const - { - return elements_.size(); - } - - const DicomElement& GetElement(size_t i) const - { - return *elements_[i]; - } - - void Print(FILE* fp) const; // For debugging only - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomElement.h --- a/Core/DicomFormat/DicomElement.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DicomValue.h" -#include "DicomTag.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC DicomElement : public boost::noncopyable - { - private: - DicomTag tag_; - DicomValue* value_; - - public: - DicomElement(uint16_t group, - uint16_t element, - const DicomValue& value) : - tag_(group, element), - value_(value.Clone()) - { - } - - DicomElement(const DicomTag& tag, - const DicomValue& value) : - tag_(tag), - value_(value.Clone()) - { - } - - ~DicomElement() - { - delete value_; - } - - const DicomTag& GetTag() const - { - return tag_; - } - - const DicomValue& GetValue() const - { - return *value_; - } - - uint16_t GetTagGroup() const - { - return tag_.GetGroup(); - } - - uint16_t GetTagElement() const - { - return tag_.GetElement(); - } - - bool operator< (const DicomElement& other) const - { - return GetTag() < other.GetTag(); - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomImageInformation.cpp --- a/Core/DicomFormat/DicomImageInformation.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,310 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include "DicomImageInformation.h" - -#include "../Compatibility.h" -#include "../OrthancException.h" -#include "../Toolbox.h" -#include -#include -#include -#include -#include - -namespace Orthanc -{ - DicomImageInformation::DicomImageInformation(const DicomMap& values) - { - unsigned int pixelRepresentation; - unsigned int planarConfiguration = 0; - - try - { - std::string p = values.GetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION).GetContent(); - Toolbox::ToUpperCase(p); - - if (p == "RGB") - { - photometric_ = PhotometricInterpretation_RGB; - } - else if (p == "MONOCHROME1") - { - photometric_ = PhotometricInterpretation_Monochrome1; - } - else if (p == "MONOCHROME2") - { - photometric_ = PhotometricInterpretation_Monochrome2; - } - else if (p == "PALETTE COLOR") - { - photometric_ = PhotometricInterpretation_Palette; - } - else if (p == "HSV") - { - photometric_ = PhotometricInterpretation_HSV; - } - else if (p == "ARGB") - { - photometric_ = PhotometricInterpretation_ARGB; - } - else if (p == "CMYK") - { - photometric_ = PhotometricInterpretation_CMYK; - } - else if (p == "YBR_FULL") - { - photometric_ = PhotometricInterpretation_YBRFull; - } - else if (p == "YBR_FULL_422") - { - photometric_ = PhotometricInterpretation_YBRFull422; - } - else if (p == "YBR_PARTIAL_420") - { - photometric_ = PhotometricInterpretation_YBRPartial420; - } - else if (p == "YBR_PARTIAL_422") - { - photometric_ = PhotometricInterpretation_YBRPartial422; - } - else if (p == "YBR_ICT") - { - photometric_ = PhotometricInterpretation_YBR_ICT; - } - else if (p == "YBR_RCT") - { - photometric_ = PhotometricInterpretation_YBR_RCT; - } - else - { - photometric_ = PhotometricInterpretation_Unknown; - } - - values.GetValue(DICOM_TAG_COLUMNS).ParseFirstUnsignedInteger(width_); // in some US images, we've seen tag values of "800\0"; that's why we parse the 'first' value - values.GetValue(DICOM_TAG_ROWS).ParseFirstUnsignedInteger(height_); - - bitsAllocated_ = boost::lexical_cast(values.GetValue(DICOM_TAG_BITS_ALLOCATED).GetContent()); - - try - { - samplesPerPixel_ = boost::lexical_cast(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).GetContent()); - } - catch (OrthancException&) - { - samplesPerPixel_ = 1; // Assume 1 color channel - } - - try - { - bitsStored_ = boost::lexical_cast(values.GetValue(DICOM_TAG_BITS_STORED).GetContent()); - } - catch (OrthancException&) - { - bitsStored_ = bitsAllocated_; - } - - try - { - highBit_ = boost::lexical_cast(values.GetValue(DICOM_TAG_HIGH_BIT).GetContent()); - } - catch (OrthancException&) - { - highBit_ = bitsStored_ - 1; - } - - try - { - pixelRepresentation = boost::lexical_cast(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).GetContent()); - } - catch (OrthancException&) - { - pixelRepresentation = 0; // Assume unsigned pixels - } - - if (samplesPerPixel_ > 1) - { - // The "Planar Configuration" is only set when "Samples per Pixels" is greater than 1 - // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.3 - try - { - planarConfiguration = boost::lexical_cast(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).GetContent()); - } - catch (OrthancException&) - { - planarConfiguration = 0; // Assume interleaved color channels - } - } - } - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_NotImplemented); - } - catch (OrthancException&) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - if (values.HasTag(DICOM_TAG_NUMBER_OF_FRAMES)) - { - try - { - numberOfFrames_ = boost::lexical_cast(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).GetContent()); - } - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_NotImplemented); - } - } - else - { - numberOfFrames_ = 1; - } - - if (bitsAllocated_ != 8 && bitsAllocated_ != 16 && - bitsAllocated_ != 24 && bitsAllocated_ != 32) - { - throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported: " + boost::lexical_cast(bitsAllocated_) + " bits allocated"); - } - else if (numberOfFrames_ == 0) - { - throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported (no frames)"); - } - else if (planarConfiguration != 0 && planarConfiguration != 1) - { - throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported: planar configuration is " + boost::lexical_cast(planarConfiguration)); - } - - if (samplesPerPixel_ == 0) - { - throw OrthancException(ErrorCode_IncompatibleImageFormat, "Image not supported: samples per pixel is 0"); - } - - bytesPerValue_ = bitsAllocated_ / 8; - - isPlanar_ = (planarConfiguration != 0 ? true : false); - isSigned_ = (pixelRepresentation != 0 ? true : false); - } - - DicomImageInformation* DicomImageInformation::Clone() const - { - std::unique_ptr target(new DicomImageInformation); - target->width_ = width_; - target->height_ = height_; - target->samplesPerPixel_ = samplesPerPixel_; - target->numberOfFrames_ = numberOfFrames_; - target->isPlanar_ = isPlanar_; - target->isSigned_ = isSigned_; - target->bytesPerValue_ = bytesPerValue_; - target->bitsAllocated_ = bitsAllocated_; - target->bitsStored_ = bitsStored_; - target->highBit_ = highBit_; - target->photometric_ = photometric_; - - return target.release(); - } - - bool DicomImageInformation::ExtractPixelFormat(PixelFormat& format, - bool ignorePhotometricInterpretation) const - { - if (photometric_ == PhotometricInterpretation_Palette) - { - if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned()) - { - format = PixelFormat_RGB24; - return true; - } - - if (GetBitsStored() == 16 && GetChannelCount() == 1 && !IsSigned()) - { - format = PixelFormat_RGB48; - return true; - } - } - - if (ignorePhotometricInterpretation || - photometric_ == PhotometricInterpretation_Monochrome1 || - photometric_ == PhotometricInterpretation_Monochrome2) - { - if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned()) - { - format = PixelFormat_Grayscale8; - return true; - } - - if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && !IsSigned()) - { - format = PixelFormat_Grayscale16; - return true; - } - - if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && IsSigned()) - { - format = PixelFormat_SignedGrayscale16; - return true; - } - - if (GetBitsAllocated() == 32 && GetChannelCount() == 1 && !IsSigned()) - { - format = PixelFormat_Grayscale32; - return true; - } - } - - if (GetBitsStored() == 8 && - GetChannelCount() == 3 && - !IsSigned() && - (ignorePhotometricInterpretation || photometric_ == PhotometricInterpretation_RGB)) - { - format = PixelFormat_RGB24; - return true; - } - - return false; - } - - - size_t DicomImageInformation::GetFrameSize() const - { - return (GetHeight() * - GetWidth() * - GetBytesPerValue() * - GetChannelCount()); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomImageInformation.h --- a/Core/DicomFormat/DicomImageInformation.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DicomMap.h" - -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC DicomImageInformation - { - private: - unsigned int width_; - unsigned int height_; - unsigned int samplesPerPixel_; - unsigned int numberOfFrames_; - - bool isPlanar_; - bool isSigned_; - size_t bytesPerValue_; - - unsigned int bitsAllocated_; - unsigned int bitsStored_; - unsigned int highBit_; - - PhotometricInterpretation photometric_; - - protected: - explicit DicomImageInformation() - { - } - - public: - explicit DicomImageInformation(const DicomMap& values); - - DicomImageInformation* Clone() const; - - unsigned int GetWidth() const - { - return width_; - } - - unsigned int GetHeight() const - { - return height_; - } - - unsigned int GetNumberOfFrames() const - { - return numberOfFrames_; - } - - unsigned int GetChannelCount() const - { - return samplesPerPixel_; - } - - unsigned int GetBitsStored() const - { - return bitsStored_; - } - - size_t GetBytesPerValue() const - { - return bytesPerValue_; - } - - bool IsSigned() const - { - return isSigned_; - } - - unsigned int GetBitsAllocated() const - { - return bitsAllocated_; - } - - unsigned int GetHighBit() const - { - return highBit_; - } - - bool IsPlanar() const - { - return isPlanar_; - } - - unsigned int GetShift() const - { - return highBit_ + 1 - bitsStored_; - } - - PhotometricInterpretation GetPhotometricInterpretation() const - { - return photometric_; - } - - bool ExtractPixelFormat(PixelFormat& format, - bool ignorePhotometricInterpretation) const; - - size_t GetFrameSize() const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomInstanceHasher.cpp --- a/Core/DicomFormat/DicomInstanceHasher.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomInstanceHasher.h" - -#include "../OrthancException.h" -#include "../Toolbox.h" - -namespace Orthanc -{ - void DicomInstanceHasher::Setup(const std::string& patientId, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid) - { - patientId_ = patientId; - studyUid_ = studyUid; - seriesUid_ = seriesUid; - instanceUid_ = instanceUid; - - if (studyUid_.size() == 0 || - seriesUid_.size() == 0 || - instanceUid_.size() == 0) - { - throw OrthancException(ErrorCode_BadFileFormat, "missing StudyInstanceUID, SeriesInstanceUID or SOPInstanceUID"); - } - } - - DicomInstanceHasher::DicomInstanceHasher(const DicomMap& instance) - { - const DicomValue* patientId = instance.TestAndGetValue(DICOM_TAG_PATIENT_ID); - - Setup(patientId == NULL ? "" : patientId->GetContent(), - instance.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent(), - instance.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent(), - instance.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent()); - } - - const std::string& DicomInstanceHasher::HashPatient() - { - if (patientHash_.size() == 0) - { - Toolbox::ComputeSHA1(patientHash_, patientId_); - } - - return patientHash_; - } - - const std::string& DicomInstanceHasher::HashStudy() - { - if (studyHash_.size() == 0) - { - Toolbox::ComputeSHA1(studyHash_, patientId_ + "|" + studyUid_); - } - - return studyHash_; - } - - const std::string& DicomInstanceHasher::HashSeries() - { - if (seriesHash_.size() == 0) - { - Toolbox::ComputeSHA1(seriesHash_, patientId_ + "|" + studyUid_ + "|" + seriesUid_); - } - - return seriesHash_; - } - - const std::string& DicomInstanceHasher::HashInstance() - { - if (instanceHash_.size() == 0) - { - Toolbox::ComputeSHA1(instanceHash_, patientId_ + "|" + studyUid_ + "|" + seriesUid_ + "|" + instanceUid_); - } - - return instanceHash_; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomInstanceHasher.h --- a/Core/DicomFormat/DicomInstanceHasher.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DicomMap.h" - -namespace Orthanc -{ - /** - * This class implements the hashing mechanism that is used to - * convert DICOM unique identifiers to Orthanc identifiers. Any - * Orthanc identifier for a DICOM resource corresponds to the SHA-1 - * hash of the DICOM identifiers. - - * \note SHA-1 hash is used because it is less sensitive to - * collision attacks than MD5. [Reference] - **/ - class DicomInstanceHasher - { - private: - std::string patientId_; - std::string studyUid_; - std::string seriesUid_; - std::string instanceUid_; - - std::string patientHash_; - std::string studyHash_; - std::string seriesHash_; - std::string instanceHash_; - - void Setup(const std::string& patientId, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid); - - public: - DicomInstanceHasher(const DicomMap& instance); - - DicomInstanceHasher(const std::string& patientId, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid) - { - Setup(patientId, studyUid, seriesUid, instanceUid); - } - - const std::string& GetPatientId() const - { - return patientId_; - } - - const std::string& GetStudyUid() const - { - return studyUid_; - } - - const std::string& GetSeriesUid() const - { - return seriesUid_; - } - - const std::string& GetInstanceUid() const - { - return instanceUid_; - } - - const std::string& HashPatient(); - - const std::string& HashStudy(); - - const std::string& HashSeries(); - - const std::string& HashInstance(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomIntegerPixelAccessor.cpp --- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,204 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include "DicomIntegerPixelAccessor.h" - -#include "../OrthancException.h" -#include -#include -#include -#include - -namespace Orthanc -{ - DicomIntegerPixelAccessor::DicomIntegerPixelAccessor(const DicomMap& values, - const void* pixelData, - size_t size) : - information_(values), - pixelData_(pixelData), - size_(size) - { - if (information_.GetBitsAllocated() > 32 || - information_.GetBitsStored() >= 32) - { - // Not available, as the accessor internally uses int32_t values - throw OrthancException(ErrorCode_NotImplemented); - } - - frame_ = 0; - frameOffset_ = information_.GetFrameSize(); - - if (information_.GetNumberOfFrames() * frameOffset_ > size) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - if (information_.IsSigned()) - { - // Pixels are signed - mask_ = (1 << (information_.GetBitsStored() - 1)) - 1; - signMask_ = (1 << (information_.GetBitsStored() - 1)); - } - else - { - // Pixels are unsigned - mask_ = (1 << information_.GetBitsStored()) - 1; - signMask_ = 0; - } - - if (information_.IsPlanar()) - { - /** - * Each color plane shall be sent contiguously. For RGB images, - * this means the order of the pixel values sent is R1, R2, R3, - * ..., G1, G2, G3, ..., B1, B2, B3, etc. - **/ - rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue(); - } - else - { - /** - * The sample values for the first pixel are followed by the - * sample values for the second pixel, etc. For RGB images, this - * means the order of the pixel values sent shall be R1, G1, B1, - * R2, G2, B2, ..., etc. - **/ - rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue() * information_.GetChannelCount(); - } - } - - - void DicomIntegerPixelAccessor::GetExtremeValues(int32_t& min, - int32_t& max) const - { - if (information_.GetHeight() == 0 || information_.GetWidth() == 0) - { - min = max = 0; - return; - } - - min = std::numeric_limits::max(); - max = std::numeric_limits::min(); - - for (unsigned int y = 0; y < information_.GetHeight(); y++) - { - for (unsigned int x = 0; x < information_.GetWidth(); x++) - { - for (unsigned int c = 0; c < information_.GetChannelCount(); c++) - { - int32_t v = GetValue(x, y); - if (v < min) - min = v; - if (v > max) - max = v; - } - } - } - } - - - int32_t DicomIntegerPixelAccessor::GetValue(unsigned int x, - unsigned int y, - unsigned int channel) const - { - assert(x < information_.GetWidth() && - y < information_.GetHeight() && - channel < information_.GetChannelCount()); - - const uint8_t* pixel = reinterpret_cast(pixelData_) + - y * rowOffset_ + frame_ * frameOffset_; - - // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.3 - if (information_.IsPlanar()) - { - /** - * Each color plane shall be sent contiguously. For RGB images, - * this means the order of the pixel values sent is R1, R2, R3, - * ..., G1, G2, G3, ..., B1, B2, B3, etc. - **/ - assert(frameOffset_ % information_.GetChannelCount() == 0); - pixel += channel * frameOffset_ / information_.GetChannelCount() + x * information_.GetBytesPerValue(); - } - else - { - /** - * The sample values for the first pixel are followed by the - * sample values for the second pixel, etc. For RGB images, this - * means the order of the pixel values sent shall be R1, G1, B1, - * R2, G2, B2, ..., etc. - **/ - pixel += channel * information_.GetBytesPerValue() + x * information_.GetChannelCount() * information_.GetBytesPerValue(); - } - - uint32_t v; - v = pixel[0]; - if (information_.GetBytesPerValue() >= 2) - v = v + (static_cast(pixel[1]) << 8); - if (information_.GetBytesPerValue() >= 3) - v = v + (static_cast(pixel[2]) << 16); - if (information_.GetBytesPerValue() >= 4) - v = v + (static_cast(pixel[3]) << 24); - - v = v >> information_.GetShift(); - - if (v & signMask_) - { - // Signed value - // http://en.wikipedia.org/wiki/Two%27s_complement#Subtraction_from_2N - return -static_cast(mask_) + static_cast(v & mask_) - 1; - } - else - { - // Unsigned value - return static_cast(v & mask_); - } - } - - - void DicomIntegerPixelAccessor::SetCurrentFrame(unsigned int frame) - { - if (frame >= information_.GetNumberOfFrames()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - frame_ = frame; - } - -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomIntegerPixelAccessor.h --- a/Core/DicomFormat/DicomIntegerPixelAccessor.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DicomMap.h" - -#include "DicomImageInformation.h" - -#include - -namespace Orthanc -{ - class DicomIntegerPixelAccessor - { - private: - DicomImageInformation information_; - - uint32_t signMask_; - uint32_t mask_; - - const void* pixelData_; - size_t size_; - unsigned int frame_; - size_t frameOffset_; - size_t rowOffset_; - - public: - DicomIntegerPixelAccessor(const DicomMap& values, - const void* pixelData, - size_t size); - - const DicomImageInformation GetInformation() const - { - return information_; - } - - unsigned int GetCurrentFrame() const - { - return frame_; - } - - void SetCurrentFrame(unsigned int frame); - - void GetExtremeValues(int32_t& min, - int32_t& max) const; - - int32_t GetValue(unsigned int x, unsigned int y, unsigned int channel = 0) const; - - const void* GetPixelData() const - { - return pixelData_; - } - - size_t GetSize() const - { - return size_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomMap.cpp --- a/Core/DicomFormat/DicomMap.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1424 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomMap.h" - -#include -#include - -#include "../Compatibility.h" -#include "../Endianness.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "../Toolbox.h" -#include "DicomArray.h" - - -namespace Orthanc -{ - namespace - { - struct MainDicomTag - { - const DicomTag tag_; - const char* name_; - }; - } - - static const MainDicomTag PATIENT_MAIN_DICOM_TAGS[] = - { - // { DicomTag(0x0010, 0x1010), "PatientAge" }, - // { DicomTag(0x0010, 0x1040), "PatientAddress" }, - { DicomTag(0x0010, 0x0010), "PatientName" }, - { DicomTag(0x0010, 0x0030), "PatientBirthDate" }, - { DicomTag(0x0010, 0x0040), "PatientSex" }, - { DicomTag(0x0010, 0x1000), "OtherPatientIDs" }, - { DICOM_TAG_PATIENT_ID, "PatientID" } - }; - - static const MainDicomTag STUDY_MAIN_DICOM_TAGS[] = - { - // { DicomTag(0x0010, 0x1020), "PatientSize" }, - // { DicomTag(0x0010, 0x1030), "PatientWeight" }, - { DICOM_TAG_STUDY_DATE, "StudyDate" }, - { DicomTag(0x0008, 0x0030), "StudyTime" }, - { DicomTag(0x0020, 0x0010), "StudyID" }, - { DICOM_TAG_STUDY_DESCRIPTION, "StudyDescription" }, - { DICOM_TAG_ACCESSION_NUMBER, "AccessionNumber" }, - { DICOM_TAG_STUDY_INSTANCE_UID, "StudyInstanceUID" }, - - // New in db v6 - { DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION, "RequestedProcedureDescription" }, - { DICOM_TAG_INSTITUTION_NAME, "InstitutionName" }, - { DICOM_TAG_REQUESTING_PHYSICIAN, "RequestingPhysician" }, - { DICOM_TAG_REFERRING_PHYSICIAN_NAME, "ReferringPhysicianName" } - }; - - static const MainDicomTag SERIES_MAIN_DICOM_TAGS[] = - { - // { DicomTag(0x0010, 0x1080), "MilitaryRank" }, - { DicomTag(0x0008, 0x0021), "SeriesDate" }, - { DicomTag(0x0008, 0x0031), "SeriesTime" }, - { DICOM_TAG_MODALITY, "Modality" }, - { DicomTag(0x0008, 0x0070), "Manufacturer" }, - { DicomTag(0x0008, 0x1010), "StationName" }, - { DICOM_TAG_SERIES_DESCRIPTION, "SeriesDescription" }, - { DicomTag(0x0018, 0x0015), "BodyPartExamined" }, - { DicomTag(0x0018, 0x0024), "SequenceName" }, - { DicomTag(0x0018, 0x1030), "ProtocolName" }, - { DicomTag(0x0020, 0x0011), "SeriesNumber" }, - { DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, "CardiacNumberOfImages" }, - { DICOM_TAG_IMAGES_IN_ACQUISITION, "ImagesInAcquisition" }, - { DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "NumberOfTemporalPositions" }, - { DICOM_TAG_NUMBER_OF_SLICES, "NumberOfSlices" }, - { DICOM_TAG_NUMBER_OF_TIME_SLICES, "NumberOfTimeSlices" }, - { DICOM_TAG_SERIES_INSTANCE_UID, "SeriesInstanceUID" }, - - // New in db v6 - { DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "ImageOrientationPatient" }, - { DICOM_TAG_SERIES_TYPE, "SeriesType" }, - { DICOM_TAG_OPERATOR_NAME, "OperatorsName" }, - { DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION, "PerformedProcedureStepDescription" }, - { DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION, "AcquisitionDeviceProcessingDescription" }, - { DICOM_TAG_CONTRAST_BOLUS_AGENT, "ContrastBolusAgent" } - }; - - static const MainDicomTag INSTANCE_MAIN_DICOM_TAGS[] = - { - { DicomTag(0x0008, 0x0012), "InstanceCreationDate" }, - { DicomTag(0x0008, 0x0013), "InstanceCreationTime" }, - { DicomTag(0x0020, 0x0012), "AcquisitionNumber" }, - { DICOM_TAG_IMAGE_INDEX, "ImageIndex" }, - { DICOM_TAG_INSTANCE_NUMBER, "InstanceNumber" }, - { DICOM_TAG_NUMBER_OF_FRAMES, "NumberOfFrames" }, - { DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, "TemporalPositionIdentifier" }, - { DICOM_TAG_SOP_INSTANCE_UID, "SOPInstanceUID" }, - - // New in db v6 - { DICOM_TAG_IMAGE_POSITION_PATIENT, "ImagePositionPatient" }, - { DICOM_TAG_IMAGE_COMMENTS, "ImageComments" }, - - /** - * Main DICOM tags that are not part of any release of the - * database schema yet, and that will be part of future db v7. In - * the meantime, the user must call "/tools/reconstruct" once to - * access these tags if the corresponding DICOM files where - * indexed in the database by an older version of Orthanc. - **/ - { DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "ImageOrientationPatient" } // New in Orthanc 1.4.2 - }; - - - static void LoadMainDicomTags(const MainDicomTag*& tags, - size_t& size, - ResourceType level) - { - switch (level) - { - case ResourceType_Patient: - tags = PATIENT_MAIN_DICOM_TAGS; - size = sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); - break; - - case ResourceType_Study: - tags = STUDY_MAIN_DICOM_TAGS; - size = sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); - break; - - case ResourceType_Series: - tags = SERIES_MAIN_DICOM_TAGS; - size = sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); - break; - - case ResourceType_Instance: - tags = INSTANCE_MAIN_DICOM_TAGS; - size = sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - static void LoadMainDicomTags(std::map& target, - ResourceType level) - { - const MainDicomTag* tags = NULL; - size_t size; - LoadMainDicomTags(tags, size, level); - - assert(tags != NULL && - size != 0); - - for (size_t i = 0; i < size; i++) - { - assert(target.find(tags[i].tag_) == target.end()); - - target[tags[i].tag_] = tags[i].name_; - } - } - - - namespace - { - class DicomTag2 : public DicomTag - { - public: - DicomTag2() : - DicomTag(0, 0) // To make std::map<> happy - { - } - - DicomTag2(const DicomTag& tag) : - DicomTag(tag) - { - } - }; - } - - - static void LoadMainDicomTags(std::map& target, - ResourceType level) - { - const MainDicomTag* tags = NULL; - size_t size; - LoadMainDicomTags(tags, size, level); - - assert(tags != NULL && - size != 0); - - for (size_t i = 0; i < size; i++) - { - assert(target.find(tags[i].name_) == target.end()); - - target[tags[i].name_] = tags[i].tag_; - } - } - - - void DicomMap::SetValueInternal(uint16_t group, - uint16_t element, - DicomValue* value) - { - DicomTag tag(group, element); - Content::iterator it = content_.find(tag); - - if (it != content_.end()) - { - delete it->second; - it->second = value; - } - else - { - content_.insert(std::make_pair(tag, value)); - } - } - - - void DicomMap::Clear() - { - for (Content::iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(it->second != NULL); - delete it->second; - } - - content_.clear(); - } - - - static void ExtractTags(DicomMap& result, - const DicomMap::Content& source, - const MainDicomTag* tags, - size_t count) - { - result.Clear(); - - for (unsigned int i = 0; i < count; i++) - { - DicomMap::Content::const_iterator it = source.find(tags[i].tag_); - if (it != source.end()) - { - result.SetValue(it->first, *it->second /* value will be cloned */); - } - } - } - - - void DicomMap::ExtractPatientInformation(DicomMap& result) const - { - ExtractTags(result, content_, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); - } - - void DicomMap::ExtractStudyInformation(DicomMap& result) const - { - ExtractTags(result, content_, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); - } - - void DicomMap::ExtractSeriesInformation(DicomMap& result) const - { - ExtractTags(result, content_, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); - } - - void DicomMap::ExtractInstanceInformation(DicomMap& result) const - { - ExtractTags(result, content_, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); - } - - - - DicomMap* DicomMap::Clone() const - { - std::unique_ptr result(new DicomMap); - - for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) - { - result->content_.insert(std::make_pair(it->first, it->second->Clone())); - } - - return result.release(); - } - - - void DicomMap::Assign(const DicomMap& other) - { - Clear(); - - for (Content::const_iterator it = other.content_.begin(); it != other.content_.end(); ++it) - { - content_.insert(std::make_pair(it->first, it->second->Clone())); - } - } - - - const DicomValue& DicomMap::GetValue(const DicomTag& tag) const - { - const DicomValue* value = TestAndGetValue(tag); - - if (value) - { - return *value; - } - else - { - throw OrthancException(ErrorCode_InexistentTag); - } - } - - - const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const - { - Content::const_iterator it = content_.find(tag); - - if (it == content_.end()) - { - return NULL; - } - else - { - return it->second; - } - } - - - void DicomMap::Remove(const DicomTag& tag) - { - Content::iterator it = content_.find(tag); - if (it != content_.end()) - { - delete it->second; - content_.erase(it); - } - } - - - static void SetupFindTemplate(DicomMap& result, - const MainDicomTag* tags, - size_t count) - { - result.Clear(); - - for (size_t i = 0; i < count; i++) - { - result.SetValue(tags[i].tag_, "", false); - } - } - - void DicomMap::SetupFindPatientTemplate(DicomMap& result) - { - SetupFindTemplate(result, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); - } - - void DicomMap::SetupFindStudyTemplate(DicomMap& result) - { - SetupFindTemplate(result, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); - result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); - result.SetValue(DICOM_TAG_PATIENT_ID, "", false); - - // These main DICOM tags are only indirectly related to the - // General Study Module, remove them - result.Remove(DICOM_TAG_INSTITUTION_NAME); - result.Remove(DICOM_TAG_REQUESTING_PHYSICIAN); - result.Remove(DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION); - } - - void DicomMap::SetupFindSeriesTemplate(DicomMap& result) - { - SetupFindTemplate(result, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); - result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); - result.SetValue(DICOM_TAG_PATIENT_ID, "", false); - result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); - - // These tags are considered as "main" by Orthanc, but are not in the Series module - result.Remove(DicomTag(0x0008, 0x0070)); // Manufacturer - result.Remove(DicomTag(0x0008, 0x1010)); // Station name - result.Remove(DicomTag(0x0018, 0x0024)); // Sequence name - result.Remove(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES); - result.Remove(DICOM_TAG_IMAGES_IN_ACQUISITION); - result.Remove(DICOM_TAG_NUMBER_OF_SLICES); - result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS); - result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES); - result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT); - result.Remove(DICOM_TAG_SERIES_TYPE); - result.Remove(DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION); - result.Remove(DICOM_TAG_CONTRAST_BOLUS_AGENT); - } - - void DicomMap::SetupFindInstanceTemplate(DicomMap& result) - { - SetupFindTemplate(result, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); - result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); - result.SetValue(DICOM_TAG_PATIENT_ID, "", false); - result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); - result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false); - } - - - void DicomMap::CopyTagIfExists(const DicomMap& source, - const DicomTag& tag) - { - if (source.HasTag(tag)) - { - SetValue(tag, source.GetValue(tag)); - } - } - - - bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level) - { - const MainDicomTag *tags = NULL; - size_t size; - LoadMainDicomTags(tags, size, level); - - for (size_t i = 0; i < size; i++) - { - if (tags[i].tag_ == tag) - { - return true; - } - } - - return false; - } - - bool DicomMap::IsMainDicomTag(const DicomTag& tag) - { - return (IsMainDicomTag(tag, ResourceType_Patient) || - IsMainDicomTag(tag, ResourceType_Study) || - IsMainDicomTag(tag, ResourceType_Series) || - IsMainDicomTag(tag, ResourceType_Instance)); - } - - - void DicomMap::GetMainDicomTagsInternal(std::set& result, ResourceType level) - { - const MainDicomTag *tags = NULL; - size_t size; - LoadMainDicomTags(tags, size, level); - - for (size_t i = 0; i < size; i++) - { - result.insert(tags[i].tag_); - } - } - - - void DicomMap::GetMainDicomTags(std::set& result, ResourceType level) - { - result.clear(); - GetMainDicomTagsInternal(result, level); - } - - - void DicomMap::GetMainDicomTags(std::set& result) - { - result.clear(); - GetMainDicomTagsInternal(result, ResourceType_Patient); - GetMainDicomTagsInternal(result, ResourceType_Study); - GetMainDicomTagsInternal(result, ResourceType_Series); - GetMainDicomTagsInternal(result, ResourceType_Instance); - } - - - void DicomMap::GetTags(std::set& tags) const - { - tags.clear(); - - for (Content::const_iterator it = content_.begin(); - it != content_.end(); ++it) - { - tags.insert(it->first); - } - } - - - static uint16_t ReadUnsignedInteger16(const char* dicom) - { - return le16toh(*reinterpret_cast(dicom)); - } - - - static uint32_t ReadUnsignedInteger32(const char* dicom) - { - return le32toh(*reinterpret_cast(dicom)); - } - - - static bool ValidateTag(const ValueRepresentation& vr, - const std::string& value) - { - switch (vr) - { - case ValueRepresentation_ApplicationEntity: - return value.size() <= 16; - - case ValueRepresentation_AgeString: - return (value.size() == 4 && - isdigit(value[0]) && - isdigit(value[1]) && - isdigit(value[2]) && - (value[3] == 'D' || value[3] == 'W' || value[3] == 'M' || value[3] == 'Y')); - - case ValueRepresentation_AttributeTag: - return value.size() == 4; - - case ValueRepresentation_CodeString: - return value.size() <= 16; - - case ValueRepresentation_Date: - return value.size() <= 18; - - case ValueRepresentation_DecimalString: - return value.size() <= 16; - - case ValueRepresentation_DateTime: - return value.size() <= 54; - - case ValueRepresentation_FloatingPointSingle: - return value.size() == 4; - - case ValueRepresentation_FloatingPointDouble: - return value.size() == 8; - - case ValueRepresentation_IntegerString: - return value.size() <= 12; - - case ValueRepresentation_LongString: - return value.size() <= 64; - - case ValueRepresentation_LongText: - return value.size() <= 10240; - - case ValueRepresentation_OtherByte: - return true; - - case ValueRepresentation_OtherDouble: - return value.size() <= (static_cast(1) << 32) - 8; - - case ValueRepresentation_OtherFloat: - return value.size() <= (static_cast(1) << 32) - 4; - - case ValueRepresentation_OtherLong: - return true; - - case ValueRepresentation_OtherWord: - return true; - - case ValueRepresentation_PersonName: - return true; - - case ValueRepresentation_ShortString: - return value.size() <= 16; - - case ValueRepresentation_SignedLong: - return value.size() == 4; - - case ValueRepresentation_Sequence: - return true; - - case ValueRepresentation_SignedShort: - return value.size() == 2; - - case ValueRepresentation_ShortText: - return value.size() <= 1024; - - case ValueRepresentation_Time: - return value.size() <= 28; - - case ValueRepresentation_UnlimitedCharacters: - return value.size() <= (static_cast(1) << 32) - 2; - - case ValueRepresentation_UniqueIdentifier: - return value.size() <= 64; - - case ValueRepresentation_UnsignedLong: - return value.size() == 4; - - case ValueRepresentation_Unknown: - return true; - - case ValueRepresentation_UniversalResource: - return value.size() <= (static_cast(1) << 32) - 2; - - case ValueRepresentation_UnsignedShort: - return value.size() == 2; - - case ValueRepresentation_UnlimitedText: - return value.size() <= (static_cast(1) << 32) - 2; - - default: - // Assume unsupported tags are OK - return true; - } - } - - - static void RemoveTagPadding(std::string& value, - const ValueRepresentation& vr) - { - /** - * Remove padding from character strings, if need be. For the time - * being, only the UI VR is supported. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html - **/ - - switch (vr) - { - case ValueRepresentation_UniqueIdentifier: - { - /** - * "Values with a VR of UI shall be padded with a single - * trailing NULL (00H) character when necessary to achieve even - * length." - **/ - - if (!value.empty() && - value[value.size() - 1] == '\0') - { - value.resize(value.size() - 1); - } - - break; - } - - /** - * TODO implement other VR - **/ - - default: - // No padding is applicable to this VR - break; - } - } - - - static bool ReadNextTag(DicomTag& tag, - ValueRepresentation& vr, - std::string& value, - const char* dicom, - size_t size, - size_t& position) - { - /** - * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2 - * This function reads a data element with Explicit VR encoded using Little-Endian. - **/ - - if (position + 6 > size) - { - return false; - } - - tag = DicomTag(ReadUnsignedInteger16(dicom + position), - ReadUnsignedInteger16(dicom + position + 2)); - - vr = StringToValueRepresentation(std::string(dicom + position + 4, 2), true); - if (vr == ValueRepresentation_NotSupported) - { - return false; - } - - if (vr == ValueRepresentation_OtherByte || - vr == ValueRepresentation_OtherDouble || - vr == ValueRepresentation_OtherFloat || - vr == ValueRepresentation_OtherLong || - vr == ValueRepresentation_OtherWord || - vr == ValueRepresentation_Sequence || - vr == ValueRepresentation_UnlimitedCharacters || - vr == ValueRepresentation_UniversalResource || - vr == ValueRepresentation_UnlimitedText || - vr == ValueRepresentation_Unknown) // Note that "UN" should never appear in the Meta Information - { - if (position + 12 > size) - { - return false; - } - - uint32_t length = ReadUnsignedInteger32(dicom + position + 8); - - if (position + 12 + length > size) - { - return false; - } - - value.assign(dicom + position + 12, length); - position += (12 + length); - } - else - { - if (position + 8 > size) - { - return false; - } - - uint16_t length = ReadUnsignedInteger16(dicom + position + 6); - - if (position + 8 + length > size) - { - return false; - } - - value.assign(dicom + position + 8, length); - position += (8 + length); - } - - if (!ValidateTag(vr, value)) - { - return false; - } - - RemoveTagPadding(value, vr); - - return true; - } - - - bool DicomMap::IsDicomFile(const void* dicom, - size_t size) - { - /** - * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html - * According to Table 7.1-1, besides the "DICM" DICOM prefix, the - * file preamble (i.e. dicom[0..127]) should not be taken into - * account to determine whether the file is or is not a DICOM file. - **/ - - const uint8_t* p = reinterpret_cast(dicom); - - return (size >= 132 && - p[128] == 'D' && - p[129] == 'I' && - p[130] == 'C' && - p[131] == 'M'); - } - - - bool DicomMap::ParseDicomMetaInformation(DicomMap& result, - const void* dicom, - size_t size) - { - if (!IsDicomFile(dicom, size)) - { - return false; - } - - - /** - * The DICOM File Meta Information must be encoded using the - * Explicit VR Little Endian Transfer Syntax - * (UID=1.2.840.10008.1.2.1). - **/ - - result.Clear(); - - // First, we read the "File Meta Information Group Length" tag - // (0002,0000) to know where to stop reading the meta header - size_t position = 132; - - DicomTag tag(0x0000, 0x0000); // Dummy initialization - ValueRepresentation vr; - std::string value; - if (!ReadNextTag(tag, vr, value, reinterpret_cast(dicom), size, position) || - tag.GetGroup() != 0x0002 || - tag.GetElement() != 0x0000 || - vr != ValueRepresentation_UnsignedLong || - value.size() != 4) - { - return false; - } - - size_t stopPosition = position + ReadUnsignedInteger32(value.c_str()); - if (stopPosition > size) - { - return false; - } - - while (position < stopPosition) - { - if (ReadNextTag(tag, vr, value, reinterpret_cast(dicom), size, position)) - { - result.SetValue(tag, value, IsBinaryValueRepresentation(vr)); - } - else - { - return false; - } - } - - return true; - } - - - static std::string ValueAsString(const DicomMap& summary, - const DicomTag& tag) - { - const DicomValue& value = summary.GetValue(tag); - if (value.IsNull()) - { - return "(null)"; - } - else - { - return value.GetContent(); - } - } - - - void DicomMap::LogMissingTagsForStore() const - { - std::string s, t; - - if (HasTag(DICOM_TAG_PATIENT_ID)) - { - if (t.size() > 0) - t += ", "; - t += "PatientID=" + ValueAsString(*this, DICOM_TAG_PATIENT_ID); - } - else - { - if (s.size() > 0) - s += ", "; - s += "PatientID"; - } - - if (HasTag(DICOM_TAG_STUDY_INSTANCE_UID)) - { - if (t.size() > 0) - t += ", "; - t += "StudyInstanceUID=" + ValueAsString(*this, DICOM_TAG_STUDY_INSTANCE_UID); - } - else - { - if (s.size() > 0) - s += ", "; - s += "StudyInstanceUID"; - } - - if (HasTag(DICOM_TAG_SERIES_INSTANCE_UID)) - { - if (t.size() > 0) - t += ", "; - t += "SeriesInstanceUID=" + ValueAsString(*this, DICOM_TAG_SERIES_INSTANCE_UID); - } - else - { - if (s.size() > 0) - s += ", "; - s += "SeriesInstanceUID"; - } - - if (HasTag(DICOM_TAG_SOP_INSTANCE_UID)) - { - if (t.size() > 0) - t += ", "; - t += "SOPInstanceUID=" + ValueAsString(*this, DICOM_TAG_SOP_INSTANCE_UID); - } - else - { - if (s.size() > 0) - s += ", "; - s += "SOPInstanceUID"; - } - - if (t.size() == 0) - { - LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)"; - } - else - { - LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t; - } - } - - - bool DicomMap::LookupStringValue(std::string& result, - const DicomTag& tag, - bool allowBinary) const - { - const DicomValue* value = TestAndGetValue(tag); - - if (value == NULL) - { - return false; - } - else - { - return value->CopyToString(result, allowBinary); - } - } - - bool DicomMap::ParseInteger32(int32_t& result, - const DicomTag& tag) const - { - const DicomValue* value = TestAndGetValue(tag); - - if (value == NULL) - { - return false; - } - else - { - return value->ParseInteger32(result); - } - } - - bool DicomMap::ParseInteger64(int64_t& result, - const DicomTag& tag) const - { - const DicomValue* value = TestAndGetValue(tag); - - if (value == NULL) - { - return false; - } - else - { - return value->ParseInteger64(result); - } - } - - bool DicomMap::ParseUnsignedInteger32(uint32_t& result, - const DicomTag& tag) const - { - const DicomValue* value = TestAndGetValue(tag); - - if (value == NULL) - { - return false; - } - else - { - return value->ParseUnsignedInteger32(result); - } - } - - bool DicomMap::ParseUnsignedInteger64(uint64_t& result, - const DicomTag& tag) const - { - const DicomValue* value = TestAndGetValue(tag); - - if (value == NULL) - { - return false; - } - else - { - return value->ParseUnsignedInteger64(result); - } - } - - bool DicomMap::ParseFloat(float& result, - const DicomTag& tag) const - { - const DicomValue* value = TestAndGetValue(tag); - - if (value == NULL) - { - return false; - } - else - { - return value->ParseFloat(result); - } - } - - bool DicomMap::ParseFirstFloat(float& result, - const DicomTag& tag) const - { - const DicomValue* value = TestAndGetValue(tag); - - if (value == NULL) - { - return false; - } - else - { - return value->ParseFirstFloat(result); - } - } - - bool DicomMap::ParseDouble(double& result, - const DicomTag& tag) const - { - const DicomValue* value = TestAndGetValue(tag); - - if (value == NULL) - { - return false; - } - else - { - return value->ParseDouble(result); - } - } - - - void DicomMap::FromDicomAsJson(const Json::Value& dicomAsJson) - { - if (dicomAsJson.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Clear(); - - Json::Value::Members tags = dicomAsJson.getMemberNames(); - for (Json::Value::Members::const_iterator - it = tags.begin(); it != tags.end(); ++it) - { - DicomTag tag(0, 0); - if (!DicomTag::ParseHexadecimal(tag, it->c_str())) - { - throw OrthancException(ErrorCode_CorruptedFile); - } - - const Json::Value& value = dicomAsJson[*it]; - - if (value.type() != Json::objectValue || - !value.isMember("Type") || - !value.isMember("Value") || - value["Type"].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_CorruptedFile); - } - - if (value["Type"] == "String") - { - if (value["Value"].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_CorruptedFile); - } - else - { - SetValue(tag, value["Value"].asString(), false /* not binary */); - } - } - } - } - - - void DicomMap::Merge(const DicomMap& other) - { - for (Content::const_iterator it = other.content_.begin(); - it != other.content_.end(); ++it) - { - assert(it->second != NULL); - - if (content_.find(it->first) == content_.end()) - { - content_[it->first] = it->second->Clone(); - } - } - } - - - void DicomMap::MergeMainDicomTags(const DicomMap& other, - ResourceType level) - { - const MainDicomTag* tags = NULL; - size_t size = 0; - - LoadMainDicomTags(tags, size, level); - assert(tags != NULL && size > 0); - - for (size_t i = 0; i < size; i++) - { - Content::const_iterator found = other.content_.find(tags[i].tag_); - - if (found != other.content_.end() && - content_.find(tags[i].tag_) == content_.end()) - { - assert(found->second != NULL); - content_[tags[i].tag_] = found->second->Clone(); - } - } - } - - - void DicomMap::ExtractMainDicomTags(const DicomMap& other) - { - Clear(); - MergeMainDicomTags(other, ResourceType_Patient); - MergeMainDicomTags(other, ResourceType_Study); - MergeMainDicomTags(other, ResourceType_Series); - MergeMainDicomTags(other, ResourceType_Instance); - } - - - bool DicomMap::HasOnlyMainDicomTags() const - { - // TODO - Speed up possible by making this std::set a global variable - - std::set mainDicomTags; - GetMainDicomTags(mainDicomTags); - - for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) - { - if (mainDicomTags.find(it->first) == mainDicomTags.end()) - { - return false; - } - } - - return true; - } - - - void DicomMap::Serialize(Json::Value& target) const - { - target = Json::objectValue; - - for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(it->second != NULL); - - std::string tag = it->first.Format(); - - Json::Value value; - it->second->Serialize(value); - - target[tag] = value; - } - } - - - void DicomMap::Unserialize(const Json::Value& source) - { - Clear(); - - if (source.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value::Members tags = source.getMemberNames(); - - for (size_t i = 0; i < tags.size(); i++) - { - DicomTag tag(0, 0); - - if (!DicomTag::ParseHexadecimal(tag, tags[i].c_str()) || - content_.find(tag) != content_.end()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - std::unique_ptr value(new DicomValue); - value->Unserialize(source[tags[i]]); - - content_[tag] = value.release(); - } - } - - - void DicomMap::FromDicomWeb(const Json::Value& source) - { - static const char* const ALPHABETIC = "Alphabetic"; - static const char* const IDEOGRAPHIC = "Ideographic"; - static const char* const INLINE_BINARY = "InlineBinary"; - static const char* const PHONETIC = "Phonetic"; - static const char* const VALUE = "Value"; - static const char* const VR = "vr"; - - Clear(); - - if (source.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value::Members tags = source.getMemberNames(); - - for (size_t i = 0; i < tags.size(); i++) - { - const Json::Value& item = source[tags[i]]; - DicomTag tag(0, 0); - - if (item.type() != Json::objectValue || - !item.isMember(VR) || - item[VR].type() != Json::stringValue || - !DicomTag::ParseHexadecimal(tag, tags[i].c_str())) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - ValueRepresentation vr = StringToValueRepresentation(item[VR].asString(), false); - - if (item.isMember(INLINE_BINARY)) - { - const Json::Value& value = item[INLINE_BINARY]; - - if (value.type() == Json::stringValue) - { - std::string decoded; - Toolbox::DecodeBase64(decoded, value.asString()); - SetValue(tag, decoded, true /* binary data */); - } - } - else if (!item.isMember(VALUE)) - { - // Tag is present, but it has a null value - SetValue(tag, "", false /* not binary */); - } - else - { - const Json::Value& value = item[VALUE]; - - if (value.type() == Json::arrayValue) - { - bool supported = true; - - std::string s; - for (Json::Value::ArrayIndex i = 0; i < value.size() && supported; i++) - { - if (!s.empty()) - { - s += '\\'; - } - - switch (value[i].type()) - { - case Json::objectValue: - if (vr == ValueRepresentation_PersonName && - value[i].type() == Json::objectValue) - { - if (value[i].isMember(ALPHABETIC) && - value[i][ALPHABETIC].type() == Json::stringValue) - { - s += value[i][ALPHABETIC].asString(); - } - - bool hasIdeographic = false; - - if (value[i].isMember(IDEOGRAPHIC) && - value[i][IDEOGRAPHIC].type() == Json::stringValue) - { - s += '=' + value[i][IDEOGRAPHIC].asString(); - hasIdeographic = true; - } - - if (value[i].isMember(PHONETIC) && - value[i][PHONETIC].type() == Json::stringValue) - { - if (!hasIdeographic) - { - s += '='; - } - - s += '=' + value[i][PHONETIC].asString(); - } - } - else - { - // This is the case of sequences - supported = false; - } - - break; - - case Json::stringValue: - s += value[i].asString(); - break; - - case Json::intValue: - s += boost::lexical_cast(value[i].asInt()); - break; - - case Json::uintValue: - s += boost::lexical_cast(value[i].asUInt()); - break; - - case Json::realValue: - s += boost::lexical_cast(value[i].asDouble()); - break; - - default: - break; - } - } - - if (supported) - { - SetValue(tag, s, false /* not binary */); - } - } - } - } - } - - - std::string DicomMap::GetStringValue(const DicomTag& tag, - const std::string& defaultValue, - bool allowBinary) const - { - std::string s; - if (LookupStringValue(s, tag, allowBinary)) - { - return s; - } - else - { - return defaultValue; - } - } - - - void DicomMap::RemoveBinaryTags() - { - Content kept; - - for (Content::iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(it->second != NULL); - - if (!it->second->IsBinary() && - !it->second->IsNull()) - { - kept[it->first] = it->second; - } - else - { - delete it->second; - } - } - - content_ = kept; - } - - - void DicomMap::DumpMainDicomTags(Json::Value& target, - ResourceType level) const - { - std::map mainTags; // TODO - Create a singleton to hold this map - LoadMainDicomTags(mainTags, level); - - target = Json::objectValue; - - for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(it->second != NULL); - - if (!it->second->IsBinary() && - !it->second->IsNull()) - { - std::map::const_iterator found = mainTags.find(it->first); - - if (found != mainTags.end()) - { - target[found->second] = it->second->GetContent(); - } - } - } - } - - - void DicomMap::ParseMainDicomTags(const Json::Value& source, - ResourceType level) - { - if (source.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - std::map mainTags; // TODO - Create a singleton to hold this map - LoadMainDicomTags(mainTags, level); - - Json::Value::Members members = source.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - std::map::const_iterator found = mainTags.find(members[i]); - - if (found != mainTags.end()) - { - const Json::Value& value = source[members[i]]; - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - SetValue(found->second, value.asString(), false); - } - } - } - } - - - void DicomMap::Print(FILE* fp) const - { - DicomArray a(*this); - a.Print(fp); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomMap.h --- a/Core/DicomFormat/DicomMap.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,248 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DicomTag.h" -#include "DicomValue.h" -#include "../Enumerations.h" - -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC DicomMap : public boost::noncopyable - { - public: - typedef std::map Content; - - private: - friend class DicomArray; - friend class FromDcmtkBridge; - friend class ParsedDicomFile; - - Content content_; - - // Warning: This takes the ownership of "value" - void SetValueInternal(uint16_t group, - uint16_t element, - DicomValue* value); - - static void GetMainDicomTagsInternal(std::set& result, - ResourceType level); - - public: - DicomMap() - { - } - - ~DicomMap() - { - Clear(); - } - - size_t GetSize() const - { - return content_.size(); - } - - DicomMap* Clone() const; - - void Assign(const DicomMap& other); - - void Clear(); - - void SetNullValue(uint16_t group, - uint16_t element) - { - SetValueInternal(group, element, new DicomValue); - } - - void SetNullValue(const DicomTag& tag) - { - SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue); - } - - void SetValue(uint16_t group, - uint16_t element, - const DicomValue& value) - { - SetValueInternal(group, element, value.Clone()); - } - - void SetValue(const DicomTag& tag, - const DicomValue& value) - { - SetValueInternal(tag.GetGroup(), tag.GetElement(), value.Clone()); - } - - void SetValue(const DicomTag& tag, - const std::string& str, - bool isBinary) - { - SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue(str, isBinary)); - } - - void SetValue(uint16_t group, - uint16_t element, - const std::string& str, - bool isBinary) - { - SetValueInternal(group, element, new DicomValue(str, isBinary)); - } - - bool HasTag(uint16_t group, uint16_t element) const - { - return HasTag(DicomTag(group, element)); - } - - bool HasTag(const DicomTag& tag) const - { - return content_.find(tag) != content_.end(); - } - - const DicomValue& GetValue(uint16_t group, uint16_t element) const - { - return GetValue(DicomTag(group, element)); - } - - const DicomValue& GetValue(const DicomTag& tag) const; - - // DO NOT delete the returned value! - const DicomValue* TestAndGetValue(uint16_t group, uint16_t element) const - { - return TestAndGetValue(DicomTag(group, element)); - } - - // DO NOT delete the returned value! - const DicomValue* TestAndGetValue(const DicomTag& tag) const; - - void Remove(const DicomTag& tag); - - void ExtractPatientInformation(DicomMap& result) const; - - void ExtractStudyInformation(DicomMap& result) const; - - void ExtractSeriesInformation(DicomMap& result) const; - - void ExtractInstanceInformation(DicomMap& result) const; - - static void SetupFindPatientTemplate(DicomMap& result); - - static void SetupFindStudyTemplate(DicomMap& result); - - static void SetupFindSeriesTemplate(DicomMap& result); - - static void SetupFindInstanceTemplate(DicomMap& result); - - void CopyTagIfExists(const DicomMap& source, - const DicomTag& tag); - - static bool IsMainDicomTag(const DicomTag& tag, ResourceType level); - - static bool IsMainDicomTag(const DicomTag& tag); - - static void GetMainDicomTags(std::set& result, ResourceType level); - - static void GetMainDicomTags(std::set& result); - - void GetTags(std::set& tags) const; - - static bool IsDicomFile(const void* dicom, - size_t size); - - static bool ParseDicomMetaInformation(DicomMap& result, - const void* dicom, - size_t size); - - void LogMissingTagsForStore() const; - - bool LookupStringValue(std::string& result, - const DicomTag& tag, - bool allowBinary) const; - - bool ParseInteger32(int32_t& result, - const DicomTag& tag) const; - - bool ParseInteger64(int64_t& result, - const DicomTag& tag) const; - - bool ParseUnsignedInteger32(uint32_t& result, - const DicomTag& tag) const; - - bool ParseUnsignedInteger64(uint64_t& result, - const DicomTag& tag) const; - - bool ParseFloat(float& result, - const DicomTag& tag) const; - - bool ParseFirstFloat(float& result, - const DicomTag& tag) const; - - bool ParseDouble(double& result, - const DicomTag& tag) const; - - void FromDicomAsJson(const Json::Value& dicomAsJson); - - void Merge(const DicomMap& other); - - void MergeMainDicomTags(const DicomMap& other, - ResourceType level); - - void ExtractMainDicomTags(const DicomMap& other); - - bool HasOnlyMainDicomTags() const; - - void Serialize(Json::Value& target) const; - - void Unserialize(const Json::Value& source); - - void FromDicomWeb(const Json::Value& source); - - std::string GetStringValue(const DicomTag& tag, - const std::string& defaultValue, - bool allowBinary) const; - - void RemoveBinaryTags(); - - void DumpMainDicomTags(Json::Value& target, - ResourceType level) const; - - void ParseMainDicomTags(const Json::Value& source, - ResourceType level); - - void Print(FILE* fp) const; // For debugging only - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomTag.cpp --- a/Core/DicomFormat/DicomTag.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,323 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomTag.h" - -#include "../OrthancException.h" - -#include -#include -#include -#include - -namespace Orthanc -{ - static inline uint16_t GetCharValue(char c) - { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else - return 0; - } - - - static inline uint16_t GetTagValue(const char* c) - { - return ((GetCharValue(c[0]) << 12) + - (GetCharValue(c[1]) << 8) + - (GetCharValue(c[2]) << 4) + - GetCharValue(c[3])); - } - - - bool DicomTag::operator< (const DicomTag& other) const - { - if (group_ < other.group_) - return true; - - if (group_ > other.group_) - return false; - - return element_ < other.element_; - } - - - std::ostream& operator<< (std::ostream& o, const DicomTag& tag) - { - using namespace std; - ios_base::fmtflags state = o.flags(); - o.flags(ios::right | ios::hex); - o << "(" << setfill('0') << setw(4) << tag.GetGroup() - << "," << setw(4) << tag.GetElement() << ")"; - o.flags(state); - return o; - } - - - std::string DicomTag::Format() const - { - char b[16]; - sprintf(b, "%04x,%04x", group_, element_); - return std::string(b); - } - - - bool DicomTag::ParseHexadecimal(DicomTag& tag, - const char* value) - { - size_t length = strlen(value); - - if (length == 9 && - isxdigit(value[0]) && - isxdigit(value[1]) && - isxdigit(value[2]) && - isxdigit(value[3]) && - (value[4] == '-' || value[4] == ',') && - isxdigit(value[5]) && - isxdigit(value[6]) && - isxdigit(value[7]) && - isxdigit(value[8])) - { - uint16_t group = GetTagValue(value); - uint16_t element = GetTagValue(value + 5); - tag = DicomTag(group, element); - return true; - } - else if (length == 8 && - isxdigit(value[0]) && - isxdigit(value[1]) && - isxdigit(value[2]) && - isxdigit(value[3]) && - isxdigit(value[4]) && - isxdigit(value[5]) && - isxdigit(value[6]) && - isxdigit(value[7])) - { - uint16_t group = GetTagValue(value); - uint16_t element = GetTagValue(value + 4); - tag = DicomTag(group, element); - return true; - } - else - { - return false; - } - } - - - const char* DicomTag::GetMainTagsName() const - { - if (*this == DICOM_TAG_ACCESSION_NUMBER) - return "AccessionNumber"; - - if (*this == DICOM_TAG_SOP_INSTANCE_UID) - return "SOPInstanceUID"; - - if (*this == DICOM_TAG_PATIENT_ID) - return "PatientID"; - - if (*this == DICOM_TAG_SERIES_INSTANCE_UID) - return "SeriesInstanceUID"; - - if (*this == DICOM_TAG_STUDY_INSTANCE_UID) - return "StudyInstanceUID"; - - if (*this == DICOM_TAG_PIXEL_DATA) - return "PixelData"; - - if (*this == DICOM_TAG_IMAGE_INDEX) - return "ImageIndex"; - - if (*this == DICOM_TAG_INSTANCE_NUMBER) - return "InstanceNumber"; - - if (*this == DICOM_TAG_NUMBER_OF_SLICES) - return "NumberOfSlices"; - - if (*this == DICOM_TAG_NUMBER_OF_FRAMES) - return "NumberOfFrames"; - - if (*this == DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES) - return "CardiacNumberOfImages"; - - if (*this == DICOM_TAG_IMAGES_IN_ACQUISITION) - return "ImagesInAcquisition"; - - if (*this == DICOM_TAG_PATIENT_NAME) - return "PatientName"; - - if (*this == DICOM_TAG_IMAGE_POSITION_PATIENT) - return "ImagePositionPatient"; - - if (*this == DICOM_TAG_IMAGE_ORIENTATION_PATIENT) - return "ImageOrientationPatient"; - - // New in Orthanc 1.6.0, as tagged as "RETIRED_" since DCMTK 3.6.4 - if (*this == DICOM_TAG_OTHER_PATIENT_IDS) - return "OtherPatientIDs"; - - return ""; - } - - - void DicomTag::AddTagsForModule(std::set& target, - DicomModule module) - { - // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions - - switch (module) - { - case DicomModule_Patient: - // This is Table C.7-1 "Patient Module Attributes" (p. 373) - target.insert(DicomTag(0x0010, 0x0010)); // Patient's name - target.insert(DicomTag(0x0010, 0x0020)); // Patient ID - target.insert(DicomTag(0x0010, 0x0030)); // Patient's birth date - target.insert(DicomTag(0x0010, 0x0040)); // Patient's sex - target.insert(DicomTag(0x0008, 0x1120)); // Referenced patient sequence - target.insert(DicomTag(0x0010, 0x0032)); // Patient's birth time - target.insert(DicomTag(0x0010, 0x1000)); // Other patient IDs - target.insert(DicomTag(0x0010, 0x1002)); // Other patient IDs sequence - target.insert(DicomTag(0x0010, 0x1001)); // Other patient names - target.insert(DicomTag(0x0010, 0x2160)); // Ethnic group - target.insert(DicomTag(0x0010, 0x4000)); // Patient comments - target.insert(DicomTag(0x0010, 0x2201)); // Patient species description - target.insert(DicomTag(0x0010, 0x2202)); // Patient species code sequence - target.insert(DicomTag(0x0010, 0x2292)); // Patient breed description - target.insert(DicomTag(0x0010, 0x2293)); // Patient breed code sequence - target.insert(DicomTag(0x0010, 0x2294)); // Breed registration sequence - target.insert(DicomTag(0x0010, 0x2297)); // Responsible person - target.insert(DicomTag(0x0010, 0x2298)); // Responsible person role - target.insert(DicomTag(0x0010, 0x2299)); // Responsible organization - target.insert(DicomTag(0x0012, 0x0062)); // Patient identity removed - target.insert(DicomTag(0x0012, 0x0063)); // De-identification method - target.insert(DicomTag(0x0012, 0x0064)); // De-identification method code sequence - - // Table 10-18 ISSUER OF PATIENT ID MACRO (p. 112) - target.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID - target.insert(DicomTag(0x0010, 0x0024)); // Issuer of Patient ID qualifiers sequence - break; - - case DicomModule_Study: - // This is Table C.7-3 "General Study Module Attributes" (p. 378) - target.insert(DicomTag(0x0020, 0x000d)); // Study instance UID - target.insert(DicomTag(0x0008, 0x0020)); // Study date - target.insert(DicomTag(0x0008, 0x0030)); // Study time - target.insert(DicomTag(0x0008, 0x0090)); // Referring physician's name - target.insert(DicomTag(0x0008, 0x0096)); // Referring physician identification sequence - target.insert(DicomTag(0x0020, 0x0010)); // Study ID - target.insert(DicomTag(0x0008, 0x0050)); // Accession number - target.insert(DicomTag(0x0008, 0x0051)); // Issuer of accession number sequence - target.insert(DicomTag(0x0008, 0x1030)); // Study description - target.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of record - target.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of record identification sequence - target.insert(DicomTag(0x0008, 0x1060)); // Name of physician(s) reading study - target.insert(DicomTag(0x0008, 0x1062)); // Physician(s) reading study identification sequence - target.insert(DicomTag(0x0032, 0x1034)); // Requesting service code sequence - target.insert(DicomTag(0x0008, 0x1110)); // Referenced study sequence - target.insert(DicomTag(0x0008, 0x1032)); // Procedure code sequence - target.insert(DicomTag(0x0040, 0x1012)); // Reason for performed procedure code sequence - break; - - case DicomModule_Series: - // This is Table C.7-5 "General Series Module Attributes" (p. 385) - target.insert(DicomTag(0x0008, 0x0060)); // Modality - target.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID - target.insert(DicomTag(0x0020, 0x0011)); // Series Number - target.insert(DicomTag(0x0020, 0x0060)); // Laterality - target.insert(DicomTag(0x0008, 0x0021)); // Series Date - target.insert(DicomTag(0x0008, 0x0031)); // Series Time - target.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians’ Name - target.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence - target.insert(DicomTag(0x0018, 0x1030)); // Protocol Name - target.insert(DicomTag(0x0008, 0x103e)); // Series Description - target.insert(DicomTag(0x0008, 0x103f)); // Series Description Code Sequence - target.insert(DicomTag(0x0008, 0x1070)); // Operators' Name - target.insert(DicomTag(0x0008, 0x1072)); // Operator Identification Sequence - target.insert(DicomTag(0x0008, 0x1111)); // Referenced Performed Procedure Step Sequence - target.insert(DicomTag(0x0008, 0x1250)); // Related Series Sequence - target.insert(DicomTag(0x0018, 0x0015)); // Body Part Examined - target.insert(DicomTag(0x0018, 0x5100)); // Patient Position - target.insert(DicomTag(0x0028, 0x0108)); // Smallest Pixel Value in Series - target.insert(DicomTag(0x0029, 0x0109)); // Largest Pixel Value in Series - target.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence - target.insert(DicomTag(0x0010, 0x2210)); // Anatomical Orientation Type - - // Table 10-16 PERFORMED PROCEDURE STEP SUMMARY MACRO ATTRIBUTES - target.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID - target.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date - target.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time - target.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description - target.insert(DicomTag(0x0040, 0x0260)); // Performed Protocol Code Sequence - target.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step - break; - - case DicomModule_Instance: - // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207) - target.insert(DicomTag(0x0008, 0x0016)); // SOP Class UID - target.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID - target.insert(DicomTag(0x0008, 0x0005)); // Specific Character Set - target.insert(DicomTag(0x0008, 0x0012)); // Instance Creation Date - target.insert(DicomTag(0x0008, 0x0013)); // Instance Creation Time - target.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID - target.insert(DicomTag(0x0008, 0x001a)); // Related General SOP Class UID - target.insert(DicomTag(0x0008, 0x001b)); // Original Specialized SOP Class UID - target.insert(DicomTag(0x0008, 0x0110)); // Coding Scheme Identification Sequence - target.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC - target.insert(DicomTag(0x0018, 0xa001)); // Contributing Equipment Sequence - target.insert(DicomTag(0x0020, 0x0013)); // Instance Number - target.insert(DicomTag(0x0100, 0x0410)); // SOP Instance Status - target.insert(DicomTag(0x0100, 0x0420)); // SOP Authorization DateTime - target.insert(DicomTag(0x0100, 0x0424)); // SOP Authorization Comment - target.insert(DicomTag(0x0100, 0x0426)); // Authorization Equipment Certification Number - target.insert(DicomTag(0x0400, 0x0500)); // Encrypted Attributes Sequence - target.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence - target.insert(DicomTag(0x0040, 0xa390)); // HL7 Structured Document Reference Sequence - target.insert(DicomTag(0x0028, 0x0303)); // Longitudinal Temporal Information Modified - - // Table C.12-6 "DIGITAL SIGNATURES MACRO ATTRIBUTES" (p. 1216) - target.insert(DicomTag(0x4ffe, 0x0001)); // MAC Parameters sequence - target.insert(DicomTag(0xfffa, 0xfffa)); // Digital signatures sequence - break; - - // TODO IMAGE MODULE? - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,243 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include -#include -#include - -#include "../Enumerations.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC DicomTag - { - // This must stay a POD (plain old data structure) - - private: - uint16_t group_; - uint16_t element_; - - public: - DicomTag(uint16_t group, - uint16_t element) : - group_(group), - element_(element) - { - } - - uint16_t GetGroup() const - { - return group_; - } - - uint16_t GetElement() const - { - return element_; - } - - bool IsPrivate() const - { - return group_ % 2 == 1; - } - - const char* GetMainTagsName() const; - - bool operator< (const DicomTag& other) const; - - bool operator== (const DicomTag& other) const - { - return group_ == other.group_ && element_ == other.element_; - } - - bool operator!= (const DicomTag& other) const - { - return !(*this == other); - } - - std::string Format() const; - - static bool ParseHexadecimal(DicomTag& tag, - const char* value); - - friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag); - - static void AddTagsForModule(std::set& target, - DicomModule module); - }; - - // Aliases for the most useful tags - static const DicomTag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050); - static const DicomTag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018); - static const DicomTag DICOM_TAG_PATIENT_ID(0x0010, 0x0020); - static const DicomTag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e); - static const DicomTag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d); - static const DicomTag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010); - static const DicomTag DICOM_TAG_TRANSFER_SYNTAX_UID(0x0002, 0x0010); - - static const DicomTag DICOM_TAG_IMAGE_INDEX(0x0054, 0x1330); - static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013); - - static const DicomTag DICOM_TAG_NUMBER_OF_SLICES(0x0054, 0x0081); - static const DicomTag DICOM_TAG_NUMBER_OF_TIME_SLICES(0x0054, 0x0101); - static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008); - static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090); - static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002); - static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010); - static const DicomTag DICOM_TAG_ENCAPSULATED_DOCUMENT(0x0042, 0x0011); - - static const DicomTag DICOM_TAG_STUDY_DESCRIPTION(0x0008, 0x1030); - static const DicomTag DICOM_TAG_SERIES_DESCRIPTION(0x0008, 0x103e); - static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060); - - // The following is used for "modify/anonymize" operations - static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016); - static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002); - static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID(0x0002, 0x0003); - static const DicomTag DICOM_TAG_DEIDENTIFICATION_METHOD(0x0012, 0x0063); - - // DICOM tags used for fMRI (thanks to Will Ryder) - static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105); - static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100); - - // Tags for C-FIND and C-MOVE - static const DicomTag DICOM_TAG_MESSAGE_ID(0x0000, 0x0110); - static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005); - static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052); - static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061); - - // Tags for images - static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011); - static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010); - static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002); - static const DicomTag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100); - static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101); - static const DicomTag DICOM_TAG_HIGH_BIT(0x0028, 0x0102); - static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103); - static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006); - static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004); - static const DicomTag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037); - static const DicomTag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032); - static const DicomTag DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE(0x0028, 0x0107); - static const DicomTag DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE(0x0028, 0x0106); - - // Tags related to date and time - static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022); - static const DicomTag DICOM_TAG_ACQUISITION_TIME(0x0008, 0x0032); - static const DicomTag DICOM_TAG_CONTENT_DATE(0x0008, 0x0023); - static const DicomTag DICOM_TAG_CONTENT_TIME(0x0008, 0x0033); - static const DicomTag DICOM_TAG_INSTANCE_CREATION_DATE(0x0008, 0x0012); - static const DicomTag DICOM_TAG_INSTANCE_CREATION_TIME(0x0008, 0x0013); - static const DicomTag DICOM_TAG_PATIENT_BIRTH_DATE(0x0010, 0x0030); - static const DicomTag DICOM_TAG_PATIENT_BIRTH_TIME(0x0010, 0x0032); - static const DicomTag DICOM_TAG_SERIES_DATE(0x0008, 0x0021); - static const DicomTag DICOM_TAG_SERIES_TIME(0x0008, 0x0031); - static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020); - static const DicomTag DICOM_TAG_STUDY_TIME(0x0008, 0x0030); - - // Various tags - static const DicomTag DICOM_TAG_SERIES_TYPE(0x0054, 0x1000); - static const DicomTag DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION(0x0032, 0x1060); - static const DicomTag DICOM_TAG_INSTITUTION_NAME(0x0008, 0x0080); - static const DicomTag DICOM_TAG_REQUESTING_PHYSICIAN(0x0032, 0x1032); - static const DicomTag DICOM_TAG_REFERRING_PHYSICIAN_NAME(0x0008, 0x0090); - static const DicomTag DICOM_TAG_OPERATOR_NAME(0x0008, 0x1070); - static const DicomTag DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION(0x0040, 0x0254); - static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000); - static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400); - static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_CODE(0x0018, 0x1401); - static const DicomTag DICOM_TAG_CASSETTE_ORIENTATION(0x0018, 0x1402); - static const DicomTag DICOM_TAG_CASSETTE_SIZE(0x0018, 0x1403); - static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010); - static const DicomTag DICOM_TAG_STUDY_ID(0x0020, 0x0010); - static const DicomTag DICOM_TAG_SERIES_NUMBER(0x0020, 0x0011); - static const DicomTag DICOM_TAG_PATIENT_SEX(0x0010, 0x0040); - static const DicomTag DICOM_TAG_LATERALITY(0x0020, 0x0060); - static const DicomTag DICOM_TAG_BODY_PART_EXAMINED(0x0018, 0x0015); - static const DicomTag DICOM_TAG_VIEW_POSITION(0x0018, 0x5101); - static const DicomTag DICOM_TAG_MANUFACTURER(0x0008, 0x0070); - static const DicomTag DICOM_TAG_PATIENT_ORIENTATION(0x0020, 0x0020); - static const DicomTag DICOM_TAG_PATIENT_COMMENTS(0x0010, 0x4000); - static const DicomTag DICOM_TAG_PATIENT_SPECIES_DESCRIPTION(0x0010, 0x2201); - static const DicomTag DICOM_TAG_STUDY_COMMENTS(0x0032, 0x4000); - static const DicomTag DICOM_TAG_OTHER_PATIENT_IDS(0x0010, 0x1000); - - // Tags used within the Stone of Orthanc - static const DicomTag DICOM_TAG_FRAME_INCREMENT_POINTER(0x0028, 0x0009); - static const DicomTag DICOM_TAG_GRID_FRAME_OFFSET_VECTOR(0x3004, 0x000c); - static const DicomTag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030); - static const DicomTag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052); - static const DicomTag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053); - static const DicomTag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050); - static const DicomTag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050); - static const DicomTag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051); - static const DicomTag DICOM_TAG_DOSE_GRID_SCALING(0x3004, 0x000e); - static const DicomTag DICOM_TAG_RED_PALETTE_COLOR_LOOKUP_TABLE_DATA(0x0028, 0x1201); - static const DicomTag DICOM_TAG_GREEN_PALETTE_COLOR_LOOKUP_TABLE_DATA(0x0028, 0x1202); - static const DicomTag DICOM_TAG_BLUE_PALETTE_COLOR_LOOKUP_TABLE_DATA(0x0028, 0x1203); - static const DicomTag DICOM_TAG_RED_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1101); - static const DicomTag DICOM_TAG_GREEN_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1102); - static const DicomTag DICOM_TAG_BLUE_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1103); - static const DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050); - - // Counting patients, studies and series - // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances - static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES(0x0020, 0x1200); - static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES(0x0020, 0x1202); - static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES(0x0020, 0x1204); - static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES(0x0020, 0x1206); - static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES(0x0020, 0x1208); - static const DicomTag DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES(0x0020, 0x1209); - static const DicomTag DICOM_TAG_SOP_CLASSES_IN_STUDY(0x0008, 0x0062); - - // Tags to preserve relationships during anonymization - static const DicomTag DICOM_TAG_REFERENCED_IMAGE_SEQUENCE(0x0008, 0x1140); - static const DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID(0x0008, 0x1155); - static const DicomTag DICOM_TAG_SOURCE_IMAGE_SEQUENCE(0x0008, 0x2112); - static const DicomTag DICOM_TAG_FRAME_OF_REFERENCE_UID(0x0020, 0x0052); - static const DicomTag DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID(0x3006, 0x0024); - static const DicomTag DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID(0x3006, 0x00c2); - static const DicomTag DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE(0x0040, 0xa375); - static const DicomTag DICOM_TAG_REFERENCED_SERIES_SEQUENCE(0x0008, 0x1115); - static const DicomTag DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE(0x3006, 0x0010); - static const DicomTag DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE(0x3006, 0x0012); - static const DicomTag DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE(0x3006, 0x0014); - - // Tags for DICOMDIR - static const DicomTag DICOM_TAG_DIRECTORY_RECORD_TYPE(0x0004, 0x1430); - static const DicomTag DICOM_TAG_OFFSET_OF_THE_NEXT_DIRECTORY_RECORD(0x0004, 0x1400); - static const DicomTag DICOM_TAG_OFFSET_OF_REFERENCED_LOWER_LEVEL_DIRECTORY_ENTITY(0x0004, 0x1420); - static const DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID_IN_FILE(0x0004, 0x1511); - static const DicomTag DICOM_TAG_REFERENCED_FILE_ID(0x0004, 0x1500); -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomValue.cpp --- a/Core/DicomFormat/DicomValue.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,322 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomValue.h" - -#include "../OrthancException.h" -#include "../SerializationToolbox.h" -#include "../Toolbox.h" - -#include - -namespace Orthanc -{ - DicomValue::DicomValue(const DicomValue& other) : - type_(other.type_), - content_(other.content_) - { - } - - - DicomValue::DicomValue(const std::string& content, - bool isBinary) : - type_(isBinary ? Type_Binary : Type_String), - content_(content) - { - } - - - DicomValue::DicomValue(const char* data, - size_t size, - bool isBinary) : - type_(isBinary ? Type_Binary : Type_String) - { - content_.assign(data, size); - } - - - const std::string& DicomValue::GetContent() const - { - if (type_ == Type_Null) - { - throw OrthancException(ErrorCode_BadParameterType); - } - else - { - return content_; - } - } - - - DicomValue* DicomValue::Clone() const - { - return new DicomValue(*this); - } - - -#if ORTHANC_ENABLE_BASE64 == 1 - void DicomValue::FormatDataUriScheme(std::string& target, - const std::string& mime) const - { - Toolbox::EncodeBase64(target, GetContent()); - target.insert(0, "data:" + mime + ";base64,"); - } -#endif - - // same as ParseValue but in case the value actually contains a sequence, - // it will return the first value - // this has been introduced to support invalid "width/height" DICOM tags in some US - // images where the width is stored as "800\0" ! - template - static bool ParseFirstValue(T& result, - const DicomValue& source) - { - if (source.IsBinary() || - source.IsNull()) - { - return false; - } - - try - { - std::string value = Toolbox::StripSpaces(source.GetContent()); - if (value.empty()) - { - return false; - } - - if (!allowSigned && - value[0] == '-') - { - return false; - } - - if (value.find("\\") == std::string::npos) - { - result = boost::lexical_cast(value); - return true; - } - else - { - std::vector tokens; - Toolbox::TokenizeString(tokens, value, '\\'); - - if (tokens.size() >= 1) - { - result = boost::lexical_cast(tokens[0]); - return true; - } - - return false; - } - } - catch (boost::bad_lexical_cast&) - { - return false; - } - } - - - template - static bool ParseValue(T& result, - const DicomValue& source) - { - if (source.IsBinary() || - source.IsNull()) - { - return false; - } - - try - { - std::string value = Toolbox::StripSpaces(source.GetContent()); - if (value.empty()) - { - return false; - } - - if (!allowSigned && - value[0] == '-') - { - return false; - } - - result = boost::lexical_cast(value); - return true; - } - catch (boost::bad_lexical_cast&) - { - return false; - } - } - - bool DicomValue::ParseInteger32(int32_t& result) const - { - int64_t tmp; - if (ParseValue(tmp, *this)) - { - result = static_cast(tmp); - return (tmp == static_cast(result)); // Check no overflow occurs - } - else - { - return false; - } - } - - bool DicomValue::ParseInteger64(int64_t& result) const - { - return ParseValue(result, *this); - } - - bool DicomValue::ParseUnsignedInteger32(uint32_t& result) const - { - uint64_t tmp; - if (ParseValue(tmp, *this)) - { - result = static_cast(tmp); - return (tmp == static_cast(result)); // Check no overflow occurs - } - else - { - return false; - } - } - - bool DicomValue::ParseUnsignedInteger64(uint64_t& result) const - { - return ParseValue(result, *this); - } - - bool DicomValue::ParseFloat(float& result) const - { - return ParseValue(result, *this); - } - - bool DicomValue::ParseDouble(double& result) const - { - return ParseValue(result, *this); - } - - bool DicomValue::ParseFirstFloat(float& result) const - { - return ParseFirstValue(result, *this); - } - - bool DicomValue::ParseFirstUnsignedInteger(unsigned int& result) const - { - return ParseFirstValue(result, *this); - } - - bool DicomValue::CopyToString(std::string& result, - bool allowBinary) const - { - if (IsNull()) - { - return false; - } - else if (IsBinary() && !allowBinary) - { - return false; - } - else - { - result.assign(content_); - return true; - } - } - - - static const char* KEY_TYPE = "Type"; - static const char* KEY_CONTENT = "Content"; - - void DicomValue::Serialize(Json::Value& target) const - { - target = Json::objectValue; - - switch (type_) - { - case Type_Null: - target[KEY_TYPE] = "Null"; - break; - - case Type_String: - target[KEY_TYPE] = "String"; - target[KEY_CONTENT] = content_; - break; - - case Type_Binary: - { - target[KEY_TYPE] = "Binary"; - - std::string base64; - Toolbox::EncodeBase64(base64, content_); - target[KEY_CONTENT] = base64; - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - void DicomValue::Unserialize(const Json::Value& source) - { - std::string type = SerializationToolbox::ReadString(source, KEY_TYPE); - - if (type == "Null") - { - type_ = Type_Null; - content_.clear(); - } - else if (type == "String") - { - type_ = Type_String; - content_ = SerializationToolbox::ReadString(source, KEY_CONTENT); - } - else if (type == "Binary") - { - type_ = Type_Binary; - - const std::string base64 =SerializationToolbox::ReadString(source, KEY_CONTENT); - Toolbox::DecodeBase64(content_, base64); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomFormat/DicomValue.h --- a/Core/DicomFormat/DicomValue.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include -#include -#include - -#if !defined(ORTHANC_ENABLE_BASE64) -# error The macro ORTHANC_ENABLE_BASE64 must be defined -#endif - - -namespace Orthanc -{ - class ORTHANC_PUBLIC DicomValue : public boost::noncopyable - { - private: - enum Type - { - Type_Null, - Type_String, - Type_Binary - }; - - Type type_; - std::string content_; - - DicomValue(const DicomValue& other); - - public: - DicomValue() : type_(Type_Null) - { - } - - DicomValue(const std::string& content, - bool isBinary); - - DicomValue(const char* data, - size_t size, - bool isBinary); - - const std::string& GetContent() const; - - bool IsNull() const - { - return type_ == Type_Null; - } - - bool IsBinary() const - { - return type_ == Type_Binary; - } - - DicomValue* Clone() const; - -#if ORTHANC_ENABLE_BASE64 == 1 - void FormatDataUriScheme(std::string& target, - const std::string& mime) const; - - void FormatDataUriScheme(std::string& target) const - { - FormatDataUriScheme(target, MIME_BINARY); - } -#endif - - bool CopyToString(std::string& result, - bool allowBinary) const; - - bool ParseInteger32(int32_t& result) const; - - bool ParseInteger64(int64_t& result) const; - - bool ParseUnsignedInteger32(uint32_t& result) const; - - bool ParseUnsignedInteger64(uint64_t& result) const; - - bool ParseFloat(float& result) const; - - bool ParseDouble(double& result) const; - - bool ParseFirstFloat(float& result) const; - - bool ParseFirstUnsignedInteger(unsigned int& result) const; - - void Serialize(Json::Value& target) const; - - void Unserialize(const Json::Value& source); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomAssociation.cpp --- a/Core/DicomNetworking/DicomAssociation.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,860 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomAssociation.h" - -#if !defined(DCMTK_VERSION_NUMBER) -# error The macro DCMTK_VERSION_NUMBER must be defined -#endif - -#include "../Compatibility.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "NetworkingCompatibility.h" - -#include // For dcmConnectionTimeout() -#include - -namespace Orthanc -{ - static void FillSopSequence(DcmDataset& dataset, - const DcmTagKey& tag, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::vector& failureReasons, - bool hasFailureReasons) - { - assert(sopClassUids.size() == sopInstanceUids.size() && - (hasFailureReasons ? - failureReasons.size() == sopClassUids.size() : - failureReasons.empty())); - - if (sopInstanceUids.empty()) - { - // Add an empty sequence - if (!dataset.insertEmptyElement(tag).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - else - { - for (size_t i = 0; i < sopClassUids.size(); i++) - { - std::unique_ptr item(new DcmItem); - if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() || - !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() || - (hasFailureReasons && - !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) || - !dataset.insertSequenceItem(tag, item.release()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - } - } - - - void DicomAssociation::Initialize() - { - role_ = DicomAssociationRole_Default; - isOpen_ = false; - net_ = NULL; - params_ = NULL; - assoc_ = NULL; - - // Must be after "isOpen_ = false" - ClearPresentationContexts(); - } - - - void DicomAssociation::CheckConnecting(const DicomAssociationParameters& parameters, - const OFCondition& cond) - { - try - { - CheckCondition(cond, parameters, "connecting"); - } - catch (OrthancException&) - { - CloseInternal(); - throw; - } - } - - - void DicomAssociation::CloseInternal() - { - if (assoc_ != NULL) - { - ASC_releaseAssociation(assoc_); - ASC_destroyAssociation(&assoc_); - assoc_ = NULL; - params_ = NULL; - } - else - { - if (params_ != NULL) - { - ASC_destroyAssociationParameters(¶ms_); - params_ = NULL; - } - } - - if (net_ != NULL) - { - ASC_dropNetwork(&net_); - net_ = NULL; - } - - accepted_.clear(); - isOpen_ = false; - } - - - void DicomAssociation::AddAccepted(const std::string& abstractSyntax, - DicomTransferSyntax syntax, - uint8_t presentationContextId) - { - AcceptedPresentationContexts::iterator found = accepted_.find(abstractSyntax); - - if (found == accepted_.end()) - { - std::map syntaxes; - syntaxes[syntax] = presentationContextId; - accepted_[abstractSyntax] = syntaxes; - } - else - { - if (found->second.find(syntax) != found->second.end()) - { - LOG(WARNING) << "The same transfer syntax (" - << GetTransferSyntaxUid(syntax) - << ") was accepted twice for the same abstract syntax UID (" - << abstractSyntax << ")"; - } - else - { - found->second[syntax] = presentationContextId; - } - } - } - - - DicomAssociation::~DicomAssociation() - { - try - { - Close(); - } - catch (OrthancException& e) - { - // Don't throw exception in destructors - LOG(ERROR) << "Error while destroying a DICOM association: " << e.What(); - } - } - - - void DicomAssociation::SetRole(DicomAssociationRole role) - { - if (role_ != role) - { - Close(); - role_ = role; - } - } - - - void DicomAssociation::ClearPresentationContexts() - { - Close(); - proposed_.clear(); - proposed_.reserve(MAX_PROPOSED_PRESENTATIONS); - } - - - void DicomAssociation::Open(const DicomAssociationParameters& parameters) - { - if (isOpen_) - { - return; // Already open - } - - // Timeout used during association negociation and ASC_releaseAssociation() - uint32_t acseTimeout = parameters.GetTimeout(); - if (acseTimeout == 0) - { - /** - * Timeout is disabled. Global timeout (seconds) for - * connecting to remote hosts. Default value is -1 which - * selects infinite timeout, i.e. blocking connect(). - **/ - dcmConnectionTimeout.set(-1); - acseTimeout = 10; - } - else - { - dcmConnectionTimeout.set(acseTimeout); - } - - T_ASC_SC_ROLE dcmtkRole; - switch (role_) - { - case DicomAssociationRole_Default: - dcmtkRole = ASC_SC_ROLE_DEFAULT; - break; - - case DicomAssociationRole_Scu: - dcmtkRole = ASC_SC_ROLE_SCU; - break; - - case DicomAssociationRole_Scp: - dcmtkRole = ASC_SC_ROLE_SCP; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - assert(net_ == NULL && - params_ == NULL && - assoc_ == NULL); - - if (proposed_.empty()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "No presentation context was proposed"); - } - - LOG(INFO) << "Opening a DICOM SCU connection from AET \"" - << parameters.GetLocalApplicationEntityTitle() - << "\" to AET \"" << parameters.GetRemoteModality().GetApplicationEntityTitle() - << "\" on host " << parameters.GetRemoteModality().GetHost() - << ":" << parameters.GetRemoteModality().GetPortNumber() - << " (manufacturer: " << EnumerationToString(parameters.GetRemoteModality().GetManufacturer()) << ")"; - - CheckConnecting(parameters, ASC_initializeNetwork(NET_REQUESTOR, 0, /*opt_acse_timeout*/ acseTimeout, &net_)); - CheckConnecting(parameters, ASC_createAssociationParameters(¶ms_, /*opt_maxReceivePDULength*/ ASC_DEFAULTMAXPDU)); - - // Set this application's title and the called application's title in the params - CheckConnecting(parameters, ASC_setAPTitles( - params_, parameters.GetLocalApplicationEntityTitle().c_str(), - parameters.GetRemoteModality().GetApplicationEntityTitle().c_str(), NULL)); - - // Set the network addresses of the local and remote entities - char localHost[HOST_NAME_MAX]; - gethostname(localHost, HOST_NAME_MAX - 1); - - char remoteHostAndPort[HOST_NAME_MAX]; - -#ifdef _MSC_VER - _snprintf -#else - snprintf -#endif - (remoteHostAndPort, HOST_NAME_MAX - 1, "%s:%d", - parameters.GetRemoteModality().GetHost().c_str(), - parameters.GetRemoteModality().GetPortNumber()); - - CheckConnecting(parameters, ASC_setPresentationAddresses(params_, localHost, remoteHostAndPort)); - - // Set various options - CheckConnecting(parameters, ASC_setTransportLayerType(params_, /*opt_secureConnection*/ false)); - - // Setup the list of proposed presentation contexts - unsigned int presentationContextId = 1; - for (size_t i = 0; i < proposed_.size(); i++) - { - assert(presentationContextId <= 255); - const char* abstractSyntax = proposed_[i].abstractSyntax_.c_str(); - - const std::set& source = proposed_[i].transferSyntaxes_; - - std::vector transferSyntaxes; - transferSyntaxes.reserve(source.size()); - - for (std::set::const_iterator - it = source.begin(); it != source.end(); ++it) - { - transferSyntaxes.push_back(GetTransferSyntaxUid(*it)); - } - - assert(!transferSyntaxes.empty()); - CheckConnecting(parameters, ASC_addPresentationContext( - params_, presentationContextId, abstractSyntax, - &transferSyntaxes[0], transferSyntaxes.size(), dcmtkRole)); - - presentationContextId += 2; - } - - // Do the association - CheckConnecting(parameters, ASC_requestAssociation(net_, params_, &assoc_)); - isOpen_ = true; - - // Inspect the accepted transfer syntaxes - LST_HEAD **l = ¶ms_->DULparams.acceptedPresentationContext; - if (*l != NULL) - { - DUL_PRESENTATIONCONTEXT* pc = (DUL_PRESENTATIONCONTEXT*) LST_Head(l); - LST_Position(l, (LST_NODE*)pc); - while (pc) - { - if (pc->result == ASC_P_ACCEPTANCE) - { - DicomTransferSyntax transferSyntax; - if (LookupTransferSyntax(transferSyntax, pc->acceptedTransferSyntax)) - { - AddAccepted(pc->abstractSyntax, transferSyntax, pc->presentationContextID); - } - else - { - LOG(WARNING) << "Unknown transfer syntax received from AET \"" - << parameters.GetRemoteModality().GetApplicationEntityTitle() - << "\": " << pc->acceptedTransferSyntax; - } - } - - pc = (DUL_PRESENTATIONCONTEXT*) LST_Next(l); - } - } - - if (accepted_.empty()) - { - throw OrthancException(ErrorCode_NoPresentationContext, - "Unable to negotiate a presentation context with AET \"" + - parameters.GetRemoteModality().GetApplicationEntityTitle() + "\""); - } - } - - void DicomAssociation::Close() - { - if (isOpen_) - { - CloseInternal(); - } - } - - - bool DicomAssociation::LookupAcceptedPresentationContext(std::map& target, - const std::string& abstractSyntax) const - { - if (!IsOpen()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, "Connection not opened"); - } - - AcceptedPresentationContexts::const_iterator found = accepted_.find(abstractSyntax); - - if (found == accepted_.end()) - { - return false; - } - else - { - target = found->second; - return true; - } - } - - - void DicomAssociation::ProposeGenericPresentationContext(const std::string& abstractSyntax) - { - std::set ts; - ts.insert(DicomTransferSyntax_LittleEndianImplicit); - ts.insert(DicomTransferSyntax_LittleEndianExplicit); - ts.insert(DicomTransferSyntax_BigEndianExplicit); // Retired - ProposePresentationContext(abstractSyntax, ts); - } - - - void DicomAssociation::ProposePresentationContext(const std::string& abstractSyntax, - DicomTransferSyntax transferSyntax) - { - std::set ts; - ts.insert(transferSyntax); - ProposePresentationContext(abstractSyntax, ts); - } - - - size_t DicomAssociation::GetRemainingPropositions() const - { - assert(proposed_.size() <= MAX_PROPOSED_PRESENTATIONS); - return MAX_PROPOSED_PRESENTATIONS - proposed_.size(); - } - - - void DicomAssociation::ProposePresentationContext( - const std::string& abstractSyntax, - const std::set& transferSyntaxes) - { - if (transferSyntaxes.empty()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "No transfer syntax provided"); - } - - if (proposed_.size() >= MAX_PROPOSED_PRESENTATIONS) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Too many proposed presentation contexts"); - } - - if (IsOpen()) - { - Close(); - } - - ProposedPresentationContext context; - context.abstractSyntax_ = abstractSyntax; - context.transferSyntaxes_ = transferSyntaxes; - - proposed_.push_back(context); - } - - - T_ASC_Association& DicomAssociation::GetDcmtkAssociation() const - { - if (isOpen_) - { - assert(assoc_ != NULL); - return *assoc_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "The connection is not open"); - } - } - - - T_ASC_Network& DicomAssociation::GetDcmtkNetwork() const - { - if (isOpen_) - { - assert(net_ != NULL); - return *net_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "The connection is not open"); - } - } - - - void DicomAssociation::CheckCondition(const OFCondition& cond, - const DicomAssociationParameters& parameters, - const std::string& command) - { - if (cond.bad()) - { - // Reformat the error message from DCMTK by turning multiline - // errors into a single line - - std::string s(cond.text()); - std::string info; - info.reserve(s.size()); - - bool isMultiline = false; - for (size_t i = 0; i < s.size(); i++) - { - if (s[i] == '\r') - { - // Ignore - } - else if (s[i] == '\n') - { - if (isMultiline) - { - info += "; "; - } - else - { - info += " ("; - isMultiline = true; - } - } - else - { - info.push_back(s[i]); - } - } - - if (isMultiline) - { - info += ")"; - } - - throw OrthancException(ErrorCode_NetworkProtocol, - "DicomAssociation - " + command + " to AET \"" + - parameters.GetRemoteModality().GetApplicationEntityTitle() + - "\": " + info); - } - } - - - void DicomAssociation::ReportStorageCommitment( - const DicomAssociationParameters& parameters, - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::vector& failureReasons) - { - if (sopClassUids.size() != sopInstanceUids.size() || - sopClassUids.size() != failureReasons.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - std::vector successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids; - std::vector failedReasons; - - successSopClassUids.reserve(sopClassUids.size()); - successSopInstanceUids.reserve(sopClassUids.size()); - failedSopClassUids.reserve(sopClassUids.size()); - failedSopInstanceUids.reserve(sopClassUids.size()); - failedReasons.reserve(sopClassUids.size()); - - for (size_t i = 0; i < sopClassUids.size(); i++) - { - switch (failureReasons[i]) - { - case StorageCommitmentFailureReason_Success: - successSopClassUids.push_back(sopClassUids[i]); - successSopInstanceUids.push_back(sopInstanceUids[i]); - break; - - case StorageCommitmentFailureReason_ProcessingFailure: - case StorageCommitmentFailureReason_NoSuchObjectInstance: - case StorageCommitmentFailureReason_ResourceLimitation: - case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported: - case StorageCommitmentFailureReason_ClassInstanceConflict: - case StorageCommitmentFailureReason_DuplicateTransactionUID: - failedSopClassUids.push_back(sopClassUids[i]); - failedSopInstanceUids.push_back(sopInstanceUids[i]); - failedReasons.push_back(failureReasons[i]); - break; - - default: - { - char buf[16]; - sprintf(buf, "%04xH", failureReasons[i]); - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Unsupported failure reason for storage commitment: " + std::string(buf)); - } - } - } - - DicomAssociation association; - - { - std::set transferSyntaxes; - transferSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); - transferSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); - - association.SetRole(DicomAssociationRole_Scp); - association.ProposePresentationContext(UID_StorageCommitmentPushModelSOPClass, - transferSyntaxes); - } - - association.Open(parameters); - - /** - * N-EVENT-REPORT - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 - * - * Status code: - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 - **/ - - /** - * Send the "EVENT_REPORT_RQ" request - **/ - - LOG(INFO) << "Reporting modality \"" - << parameters.GetRemoteModality().GetApplicationEntityTitle() - << "\" about storage commitment transaction: " << transactionUid - << " (" << successSopClassUids.size() << " successes, " - << failedSopClassUids.size() << " failures)"; - const DIC_US messageId = association.GetDcmtkAssociation().nextMsgID++; - - { - T_DIMSE_Message message; - memset(&message, 0, sizeof(message)); - message.CommandField = DIMSE_N_EVENT_REPORT_RQ; - - T_DIMSE_N_EventReportRQ& content = message.msg.NEventReportRQ; - content.MessageID = messageId; - strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); - strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); - content.DataSetType = DIMSE_DATASET_PRESENT; - - DcmDataset dataset; - if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - { - std::vector empty; - FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids, - successSopInstanceUids, empty, false); - } - - // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html - if (failedSopClassUids.empty()) - { - content.EventTypeID = 1; // "Storage Commitment Request Successful" - } - else - { - content.EventTypeID = 2; // "Storage Commitment Request Complete - Failures Exist" - - // Failure reason - // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 - FillSopSequence(dataset, DCM_FailedSOPSequence, failedSopClassUids, - failedSopInstanceUids, failedReasons, true); - } - - int presID = ASC_findAcceptedPresentationContextID( - &association.GetDcmtkAssociation(), UID_StorageCommitmentPushModelSOPClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to send N-EVENT-REPORT request to AET: " + - parameters.GetRemoteModality().GetApplicationEntityTitle()); - } - - if (!DIMSE_sendMessageUsingMemoryData( - &association.GetDcmtkAssociation(), presID, &message, NULL /* status detail */, - &dataset, NULL /* callback */, NULL /* callback context */, - NULL /* commandSet */).good()) - { - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - /** - * Read the "EVENT_REPORT_RSP" response - **/ - - { - T_ASC_PresentationContextID presID = 0; - T_DIMSE_Message message; - - if (!DIMSE_receiveCommand(&association.GetDcmtkAssociation(), - (parameters.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - parameters.GetTimeout(), &presID, &message, - NULL /* no statusDetail */).good() || - message.CommandField != DIMSE_N_EVENT_REPORT_RSP) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to read N-EVENT-REPORT response from AET: " + - parameters.GetRemoteModality().GetApplicationEntityTitle()); - } - - const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP; - if (content.MessageIDBeingRespondedTo != messageId || - !(content.opts & O_NEVENTREPORT_AFFECTEDSOPCLASSUID) || - !(content.opts & O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID) || - //(content.opts & O_NEVENTREPORT_EVENTTYPEID) || // Pedantic test - The "content.EventTypeID" is not used by Orthanc - std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || - std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || - content.DataSetType != DIMSE_DATASET_NULL) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Badly formatted N-EVENT-REPORT response from AET: " + - parameters.GetRemoteModality().GetApplicationEntityTitle()); - } - - if (content.DimseStatus != 0 /* success */) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "The request cannot be handled by remote AET: " + - parameters.GetRemoteModality().GetApplicationEntityTitle()); - } - } - - association.Close(); - } - - - void DicomAssociation::RequestStorageCommitment( - const DicomAssociationParameters& parameters, - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids) - { - if (sopClassUids.size() != sopInstanceUids.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - for (size_t i = 0; i < sopClassUids.size(); i++) - { - if (sopClassUids[i].empty() || - sopInstanceUids[i].empty()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "The SOP class/instance UIDs cannot be empty, found: \"" + - sopClassUids[i] + "\" / \"" + sopInstanceUids[i] + "\""); - } - } - - if (transactionUid.size() < 5 || - transactionUid.substr(0, 5) != "2.25.") - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - DicomAssociation association; - - { - std::set transferSyntaxes; - transferSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); - transferSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); - - association.SetRole(DicomAssociationRole_Default); - association.ProposePresentationContext(UID_StorageCommitmentPushModelSOPClass, - transferSyntaxes); - } - - association.Open(parameters); - - /** - * N-ACTION - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 - * - * Status code: - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8 - **/ - - /** - * Send the "N_ACTION_RQ" request - **/ - - LOG(INFO) << "Request to modality \"" - << parameters.GetRemoteModality().GetApplicationEntityTitle() - << "\" about storage commitment for " << sopClassUids.size() - << " instances, with transaction UID: " << transactionUid; - const DIC_US messageId = association.GetDcmtkAssociation().nextMsgID++; - - { - T_DIMSE_Message message; - memset(&message, 0, sizeof(message)); - message.CommandField = DIMSE_N_ACTION_RQ; - - T_DIMSE_N_ActionRQ& content = message.msg.NActionRQ; - content.MessageID = messageId; - strncpy(content.RequestedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); - strncpy(content.RequestedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); - content.ActionTypeID = 1; // "Request Storage Commitment" - content.DataSetType = DIMSE_DATASET_PRESENT; - - DcmDataset dataset; - if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - { - std::vector empty; - FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, empty, false); - } - - int presID = ASC_findAcceptedPresentationContextID( - &association.GetDcmtkAssociation(), UID_StorageCommitmentPushModelSOPClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to send N-ACTION request to AET: " + - parameters.GetRemoteModality().GetApplicationEntityTitle()); - } - - if (!DIMSE_sendMessageUsingMemoryData( - &association.GetDcmtkAssociation(), presID, &message, NULL /* status detail */, - &dataset, NULL /* callback */, NULL /* callback context */, - NULL /* commandSet */).good()) - { - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - /** - * Read the "N_ACTION_RSP" response - **/ - - { - T_ASC_PresentationContextID presID = 0; - T_DIMSE_Message message; - - if (!DIMSE_receiveCommand(&association.GetDcmtkAssociation(), - (parameters.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - parameters.GetTimeout(), &presID, &message, - NULL /* no statusDetail */).good() || - message.CommandField != DIMSE_N_ACTION_RSP) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Unable to read N-ACTION response from AET: " + - parameters.GetRemoteModality().GetApplicationEntityTitle()); - } - - const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP; - if (content.MessageIDBeingRespondedTo != messageId || - !(content.opts & O_NACTION_AFFECTEDSOPCLASSUID) || - !(content.opts & O_NACTION_AFFECTEDSOPINSTANCEUID) || - //(content.opts & O_NACTION_ACTIONTYPEID) || // Pedantic test - The "content.ActionTypeID" is not used by Orthanc - std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || - std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance || - content.DataSetType != DIMSE_DATASET_NULL) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "Badly formatted N-ACTION response from AET: " + - parameters.GetRemoteModality().GetApplicationEntityTitle()); - } - - if (content.DimseStatus != 0 /* success */) - { - throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - " - "The request cannot be handled by remote AET: " + - parameters.GetRemoteModality().GetApplicationEntityTitle()); - } - } - - association.Close(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomAssociation.h --- a/Core/DicomNetworking/DicomAssociation.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 -# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 -#endif - -#include "DicomAssociationParameters.h" - -#include - -#include // For uint8_t -#include -#include - -namespace Orthanc -{ - class DicomAssociation : public boost::noncopyable - { - private: - // This is the maximum number of presentation context IDs (the - // number of odd integers between 1 and 255) - // http://dicom.nema.org/medical/dicom/2019e/output/chtml/part08/sect_9.3.2.2.html - static const size_t MAX_PROPOSED_PRESENTATIONS = 128; - - struct ProposedPresentationContext - { - std::string abstractSyntax_; - std::set transferSyntaxes_; - }; - - typedef std::map > - AcceptedPresentationContexts; - - DicomAssociationRole role_; - bool isOpen_; - std::vector proposed_; - AcceptedPresentationContexts accepted_; - T_ASC_Network* net_; - T_ASC_Parameters* params_; - T_ASC_Association* assoc_; - - void Initialize(); - - void CheckConnecting(const DicomAssociationParameters& parameters, - const OFCondition& cond); - - void CloseInternal(); - - void AddAccepted(const std::string& abstractSyntax, - DicomTransferSyntax syntax, - uint8_t presentationContextId); - - public: - DicomAssociation() - { - Initialize(); - } - - ~DicomAssociation(); - - bool IsOpen() const - { - return isOpen_; - } - - void SetRole(DicomAssociationRole role); - - void ClearPresentationContexts(); - - void Open(const DicomAssociationParameters& parameters); - - void Close(); - - bool LookupAcceptedPresentationContext( - std::map& target, - const std::string& abstractSyntax) const; - - void ProposeGenericPresentationContext(const std::string& abstractSyntax); - - void ProposePresentationContext(const std::string& abstractSyntax, - DicomTransferSyntax transferSyntax); - - size_t GetRemainingPropositions() const; - - void ProposePresentationContext( - const std::string& abstractSyntax, - const std::set& transferSyntaxes); - - T_ASC_Association& GetDcmtkAssociation() const; - - T_ASC_Network& GetDcmtkNetwork() const; - - static void CheckCondition(const OFCondition& cond, - const DicomAssociationParameters& parameters, - const std::string& command); - - static void ReportStorageCommitment( - const DicomAssociationParameters& parameters, - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::vector& failureReasons); - - static void RequestStorageCommitment( - const DicomAssociationParameters& parameters, - const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomAssociationParameters.cpp --- a/Core/DicomNetworking/DicomAssociationParameters.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomAssociationParameters.h" - -#include "../Compatibility.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "../SerializationToolbox.h" -#include "NetworkingCompatibility.h" - -#include - -// By default, the timeout for client DICOM connections is set to 10 seconds -static boost::mutex defaultTimeoutMutex_; -static uint32_t defaultTimeout_ = 10; - - -namespace Orthanc -{ - void DicomAssociationParameters::CheckHost(const std::string& host) - { - if (host.size() > HOST_NAME_MAX - 10) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Invalid host name (too long): " + host); - } - } - - - uint32_t DicomAssociationParameters::GetDefaultTimeout() - { - boost::mutex::scoped_lock lock(defaultTimeoutMutex_); - return defaultTimeout_; - } - - - DicomAssociationParameters::DicomAssociationParameters() : - localAet_("ORTHANC"), - timeout_(GetDefaultTimeout()) - { - remote_.SetApplicationEntityTitle("ANY-SCP"); - } - - - DicomAssociationParameters::DicomAssociationParameters(const std::string& localAet, - const RemoteModalityParameters& remote) : - localAet_(localAet), - timeout_(GetDefaultTimeout()) - { - SetRemoteModality(remote); - } - - - void DicomAssociationParameters::SetRemoteModality(const RemoteModalityParameters& remote) - { - CheckHost(remote.GetHost()); - remote_ = remote; - } - - - void DicomAssociationParameters::SetRemoteHost(const std::string& host) - { - CheckHost(host); - remote_.SetHost(host); - } - - - bool DicomAssociationParameters::IsEqual(const DicomAssociationParameters& other) const - { - return (localAet_ == other.localAet_ && - remote_.GetApplicationEntityTitle() == other.remote_.GetApplicationEntityTitle() && - remote_.GetHost() == other.remote_.GetHost() && - remote_.GetPortNumber() == other.remote_.GetPortNumber() && - remote_.GetManufacturer() == other.remote_.GetManufacturer() && - timeout_ == other.timeout_); - } - - - static const char* const LOCAL_AET = "LocalAet"; - static const char* const REMOTE = "Remote"; - static const char* const TIMEOUT = "Timeout"; // New in Orthanc in 1.7.0 - - - void DicomAssociationParameters::SerializeJob(Json::Value& target) const - { - if (target.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - target[LOCAL_AET] = localAet_; - remote_.Serialize(target[REMOTE], true /* force advanced format */); - target[TIMEOUT] = timeout_; - } - } - - - DicomAssociationParameters DicomAssociationParameters::UnserializeJob(const Json::Value& serialized) - { - if (serialized.type() == Json::objectValue) - { - DicomAssociationParameters result; - - result.remote_ = RemoteModalityParameters(serialized[REMOTE]); - result.localAet_ = SerializationToolbox::ReadString(serialized, LOCAL_AET); - result.timeout_ = SerializationToolbox::ReadInteger(serialized, TIMEOUT, GetDefaultTimeout()); - - return result; - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - void DicomAssociationParameters::SetDefaultTimeout(uint32_t seconds) - { - LOG(INFO) << "Default timeout for DICOM connections if Orthanc acts as SCU (client): " - << seconds << " seconds (0 = no timeout)"; - - { - boost::mutex::scoped_lock lock(defaultTimeoutMutex_); - defaultTimeout_ = seconds; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomAssociationParameters.h --- a/Core/DicomNetworking/DicomAssociationParameters.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "RemoteModalityParameters.h" - -#include - -class OFCondition; // From DCMTK - -namespace Orthanc -{ - class DicomAssociationParameters - { - private: - std::string localAet_; - RemoteModalityParameters remote_; - uint32_t timeout_; - - static void CheckHost(const std::string& host); - - public: - DicomAssociationParameters(); - - DicomAssociationParameters(const std::string& localAet, - const RemoteModalityParameters& remote); - - const std::string& GetLocalApplicationEntityTitle() const - { - return localAet_; - } - - void SetLocalApplicationEntityTitle(const std::string& aet) - { - localAet_ = aet; - } - - const RemoteModalityParameters& GetRemoteModality() const - { - return remote_; - } - - void SetRemoteModality(const RemoteModalityParameters& parameters); - - void SetRemoteApplicationEntityTitle(const std::string& aet) - { - remote_.SetApplicationEntityTitle(aet); - } - - void SetRemoteHost(const std::string& host); - - void SetRemotePort(uint16_t port) - { - remote_.SetPortNumber(port); - } - - void SetRemoteManufacturer(ModalityManufacturer manufacturer) - { - remote_.SetManufacturer(manufacturer); - } - - bool IsEqual(const DicomAssociationParameters& other) const; - - // Setting it to "0" disables the timeout (infinite wait) - void SetTimeout(uint32_t seconds) - { - timeout_ = seconds; - } - - uint32_t GetTimeout() const - { - return timeout_; - } - - bool HasTimeout() const - { - return timeout_ != 0; - } - - void SerializeJob(Json::Value& target) const; - - static DicomAssociationParameters UnserializeJob(const Json::Value& serialized); - - static void SetDefaultTimeout(uint32_t seconds); - - static uint32_t GetDefaultTimeout(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomControlUserConnection.cpp --- a/Core/DicomNetworking/DicomControlUserConnection.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,674 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomControlUserConnection.h" - -#include "../Compatibility.h" -#include "../DicomFormat/DicomArray.h" -#include "../DicomParsing/FromDcmtkBridge.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "DicomAssociation.h" - -#include -#include - -namespace Orthanc -{ - static void TestAndCopyTag(DicomMap& result, - const DicomMap& source, - const DicomTag& tag) - { - if (!source.HasTag(tag)) - { - throw OrthancException(ErrorCode_BadRequest); - } - else - { - result.SetValue(tag, source.GetValue(tag)); - } - } - - - namespace - { - struct FindPayload - { - DicomFindAnswers* answers; - const char* level; - bool isWorklist; - }; - } - - - static void FindCallback( - /* in */ - void *callbackData, - T_DIMSE_C_FindRQ *request, /* original find request */ - int responseCount, - T_DIMSE_C_FindRSP *response, /* pending response received */ - DcmDataset *responseIdentifiers /* pending response identifiers */ - ) - { - FindPayload& payload = *reinterpret_cast(callbackData); - - if (responseIdentifiers != NULL) - { - if (payload.isWorklist) - { - ParsedDicomFile answer(*responseIdentifiers); - payload.answers->Add(answer); - } - else - { - DicomMap m; - FromDcmtkBridge::ExtractDicomSummary(m, *responseIdentifiers); - - if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level, false); - } - - payload.answers->Add(m); - } - } - } - - - static void NormalizeFindQuery(DicomMap& fixedQuery, - ResourceType level, - const DicomMap& fields) - { - std::set allowedTags; - - // WARNING: Do not add "break" or reorder items in this switch-case! - switch (level) - { - case ResourceType_Instance: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Instance); - - case ResourceType_Series: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Series); - - case ResourceType_Study: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Study); - - case ResourceType_Patient: - DicomTag::AddTagsForModule(allowedTags, DicomModule_Patient); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - switch (level) - { - case ResourceType_Patient: - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES); - break; - - case ResourceType_Study: - allowedTags.insert(DICOM_TAG_MODALITIES_IN_STUDY); - allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES); - allowedTags.insert(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES); - allowedTags.insert(DICOM_TAG_SOP_CLASSES_IN_STUDY); - break; - - case ResourceType_Series: - allowedTags.insert(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES); - break; - - default: - break; - } - - allowedTags.insert(DICOM_TAG_SPECIFIC_CHARACTER_SET); - - DicomArray query(fields); - for (size_t i = 0; i < query.GetSize(); i++) - { - const DicomTag& tag = query.GetElement(i).GetTag(); - if (allowedTags.find(tag) == allowedTags.end()) - { - LOG(WARNING) << "Tag not allowed for this C-Find level, will be ignored: " << tag; - } - else - { - fixedQuery.SetValue(tag, query.GetElement(i).GetValue()); - } - } - } - - - - static ParsedDicomFile* ConvertQueryFields(const DicomMap& fields, - ModalityManufacturer manufacturer) - { - // Fix outgoing C-Find requests issue for Syngo.Via and its - // solution was reported by Emsy Chan by private mail on - // 2015-06-17. According to Robert van Ommen (2015-11-30), the - // same fix is required for Agfa Impax. This was generalized for - // generic manufacturer since it seems to affect PhilipsADW, - // GEWAServer as well: - // https://bitbucket.org/sjodogne/orthanc/issues/31/ - - switch (manufacturer) - { - case ModalityManufacturer_GenericNoWildcardInDates: - case ModalityManufacturer_GenericNoUniversalWildcard: - { - std::unique_ptr fix(fields.Clone()); - - std::set tags; - fix->GetTags(tags); - - for (std::set::const_iterator it = tags.begin(); it != tags.end(); ++it) - { - // Replace a "*" wildcard query by an empty query ("") for - // "date" or "all" value representations depending on the - // type of manufacturer. - if (manufacturer == ModalityManufacturer_GenericNoUniversalWildcard || - (manufacturer == ModalityManufacturer_GenericNoWildcardInDates && - FromDcmtkBridge::LookupValueRepresentation(*it) == ValueRepresentation_Date)) - { - const DicomValue* value = fix->TestAndGetValue(*it); - - if (value != NULL && - !value->IsNull() && - value->GetContent() == "*") - { - fix->SetValue(*it, "", false); - } - } - } - - return new ParsedDicomFile(*fix, GetDefaultDicomEncoding(), - false /* be strict */); - } - - default: - return new ParsedDicomFile(fields, GetDefaultDicomEncoding(), - false /* be strict */); - } - } - - - - void DicomControlUserConnection::SetupPresentationContexts() - { - assert(association_.get() != NULL); - association_->ProposeGenericPresentationContext(UID_VerificationSOPClass); - association_->ProposeGenericPresentationContext(UID_FINDPatientRootQueryRetrieveInformationModel); - association_->ProposeGenericPresentationContext(UID_FINDStudyRootQueryRetrieveInformationModel); - association_->ProposeGenericPresentationContext(UID_MOVEStudyRootQueryRetrieveInformationModel); - association_->ProposeGenericPresentationContext(UID_FINDModalityWorklistInformationModel); - } - - - void DicomControlUserConnection::FindInternal(DicomFindAnswers& answers, - DcmDataset* dataset, - const char* sopClass, - bool isWorklist, - const char* level) - { - assert(isWorklist ^ (level != NULL)); - assert(association_.get() != NULL); - - association_->Open(parameters_); - - FindPayload payload; - payload.answers = &answers; - payload.level = level; - payload.isWorklist = isWorklist; - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID( - &association_->GetDcmtkAssociation(), sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomFindUnavailable, - "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle()); - } - - T_DIMSE_C_FindRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = association_->GetDcmtkAssociation().nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - - T_DIMSE_C_FindRSP response; - DcmDataset* statusDetail = NULL; - -#if DCMTK_VERSION_NUMBER >= 364 - int responseCount; -#endif - - OFCondition cond = DIMSE_findUser( - &association_->GetDcmtkAssociation(), presID, &request, dataset, -#if DCMTK_VERSION_NUMBER >= 364 - responseCount, -#endif - FindCallback, &payload, - /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ parameters_.GetTimeout(), - &response, &statusDetail); - - if (statusDetail) - { - delete statusDetail; - } - - DicomAssociation::CheckCondition(cond, parameters_, "C-FIND"); - - - /** - * New in Orthanc 1.6.0: Deal with failures during C-FIND. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#table_C.4-1 - **/ - - if (response.DimseStatus != 0x0000 && // Success - response.DimseStatus != 0xFF00 && // Pending - Matches are continuing - response.DimseStatus != 0xFF01) // Pending - Matches are continuing - { - char buf[16]; - sprintf(buf, "%04X", response.DimseStatus); - - if (response.DimseStatus == STATUS_FIND_Failed_UnableToProcess) - { - throw OrthancException(ErrorCode_NetworkProtocol, - HttpStatus_422_UnprocessableEntity, - "C-FIND SCU to AET \"" + - parameters_.GetRemoteModality().GetApplicationEntityTitle() + - "\" has failed with DIMSE status 0x" + buf + - " (unable to process - invalid query ?)"); - } - else - { - throw OrthancException(ErrorCode_NetworkProtocol, "C-FIND SCU to AET \"" + - parameters_.GetRemoteModality().GetApplicationEntityTitle() + - "\" has failed with DIMSE status 0x" + buf); - } - } - } - - - void DicomControlUserConnection::MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields) - { - assert(association_.get() != NULL); - association_->Open(parameters_); - - std::unique_ptr query( - ConvertQueryFields(fields, parameters_.GetRemoteModality().GetManufacturer())); - DcmDataset* dataset = query->GetDcmtkObject().getDataset(); - - const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel; - switch (level) - { - case ResourceType_Patient: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT"); - break; - - case ResourceType_Study: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY"); - break; - - case ResourceType_Series: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES"); - break; - - case ResourceType_Instance: - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Figure out which of the accepted presentation contexts should be used - int presID = ASC_findAcceptedPresentationContextID(&association_->GetDcmtkAssociation(), sopClass); - if (presID == 0) - { - throw OrthancException(ErrorCode_DicomMoveUnavailable, - "Remote AET is " + parameters_.GetRemoteModality().GetApplicationEntityTitle()); - } - - T_DIMSE_C_MoveRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = association_->GetDcmtkAssociation().nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClass, DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - strncpy(request.MoveDestination, targetAet.c_str(), DIC_AE_LEN); - - T_DIMSE_C_MoveRSP response; - DcmDataset* statusDetail = NULL; - DcmDataset* responseIdentifiers = NULL; - OFCondition cond = DIMSE_moveUser( - &association_->GetDcmtkAssociation(), presID, &request, dataset, NULL, NULL, - /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ parameters_.GetTimeout(), - &association_->GetDcmtkNetwork(), NULL, NULL, - &response, &statusDetail, &responseIdentifiers); - - if (statusDetail) - { - delete statusDetail; - } - - if (responseIdentifiers) - { - delete responseIdentifiers; - } - - DicomAssociation::CheckCondition(cond, parameters_, "C-MOVE"); - - - /** - * New in Orthanc 1.6.0: Deal with failures during C-MOVE. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html#table_C.4-2 - **/ - - if (response.DimseStatus != 0x0000 && // Success - response.DimseStatus != 0xFF00) // Pending - Sub-operations are continuing - { - char buf[16]; - sprintf(buf, "%04X", response.DimseStatus); - - if (response.DimseStatus == STATUS_MOVE_Failed_UnableToProcess) - { - throw OrthancException(ErrorCode_NetworkProtocol, - HttpStatus_422_UnprocessableEntity, - "C-MOVE SCU to AET \"" + - parameters_.GetRemoteModality().GetApplicationEntityTitle() + - "\" has failed with DIMSE status 0x" + buf + - " (unable to process - resource not found ?)"); - } - else - { - throw OrthancException(ErrorCode_NetworkProtocol, "C-MOVE SCU to AET \"" + - parameters_.GetRemoteModality().GetApplicationEntityTitle() + - "\" has failed with DIMSE status 0x" + buf); - } - } - } - - - DicomControlUserConnection::DicomControlUserConnection(const DicomAssociationParameters& params) : - parameters_(params), - association_(new DicomAssociation) - { - SetupPresentationContexts(); - } - - - void DicomControlUserConnection::Close() - { - assert(association_.get() != NULL); - association_->Close(); - } - - - bool DicomControlUserConnection::Echo() - { - assert(association_.get() != NULL); - association_->Open(parameters_); - - DIC_US status; - DicomAssociation::CheckCondition( - DIMSE_echoUser(&association_->GetDcmtkAssociation(), - association_->GetDcmtkAssociation().nextMsgID++, - /*opt_blockMode*/ (parameters_.HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ parameters_.GetTimeout(), - &status, NULL), - parameters_, "C-ECHO"); - - return status == STATUS_Success; - } - - - void DicomControlUserConnection::Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& originalFields, - bool normalize) - { - std::unique_ptr query; - - if (normalize) - { - DicomMap fields; - NormalizeFindQuery(fields, level, originalFields); - query.reset(ConvertQueryFields(fields, parameters_.GetRemoteModality().GetManufacturer())); - } - else - { - query.reset(new ParsedDicomFile(originalFields, GetDefaultDicomEncoding(), - false /* be strict */)); - } - - DcmDataset* dataset = query->GetDcmtkObject().getDataset(); - - const char* clevel = NULL; - const char* sopClass = NULL; - - switch (level) - { - case ResourceType_Patient: - clevel = "PATIENT"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "PATIENT"); - sopClass = UID_FINDPatientRootQueryRetrieveInformationModel; - break; - - case ResourceType_Study: - clevel = "STUDY"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "STUDY"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Series: - clevel = "SERIES"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "SERIES"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - case ResourceType_Instance: - clevel = "IMAGE"; - DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE"); - sopClass = UID_FINDStudyRootQueryRetrieveInformationModel; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - const char* universal; - if (parameters_.GetRemoteModality().GetManufacturer() == ModalityManufacturer_GE) - { - universal = "*"; - } - else - { - universal = ""; - } - - - // Add the expected tags for this query level. - // WARNING: Do not reorder or add "break" in this switch-case! - switch (level) - { - case ResourceType_Instance: - if (!dataset->tagExists(DCM_SOPInstanceUID)) - { - DU_putStringDOElement(dataset, DCM_SOPInstanceUID, universal); - } - - case ResourceType_Series: - if (!dataset->tagExists(DCM_SeriesInstanceUID)) - { - DU_putStringDOElement(dataset, DCM_SeriesInstanceUID, universal); - } - - case ResourceType_Study: - if (!dataset->tagExists(DCM_AccessionNumber)) - { - DU_putStringDOElement(dataset, DCM_AccessionNumber, universal); - } - - if (!dataset->tagExists(DCM_StudyInstanceUID)) - { - DU_putStringDOElement(dataset, DCM_StudyInstanceUID, universal); - } - - case ResourceType_Patient: - if (!dataset->tagExists(DCM_PatientID)) - { - DU_putStringDOElement(dataset, DCM_PatientID, universal); - } - - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - assert(clevel != NULL && sopClass != NULL); - FindInternal(result, dataset, sopClass, false, clevel); - } - - - void DicomControlUserConnection::Move(const std::string& targetAet, - ResourceType level, - const DicomMap& findResult) - { - DicomMap move; - switch (level) - { - case ResourceType_Patient: - TestAndCopyTag(move, findResult, DICOM_TAG_PATIENT_ID); - break; - - case ResourceType_Study: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - break; - - case ResourceType_Series: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - break; - - case ResourceType_Instance: - TestAndCopyTag(move, findResult, DICOM_TAG_STUDY_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SERIES_INSTANCE_UID); - TestAndCopyTag(move, findResult, DICOM_TAG_SOP_INSTANCE_UID); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - MoveInternal(targetAet, level, move); - } - - - void DicomControlUserConnection::Move(const std::string& targetAet, - const DicomMap& findResult) - { - if (!findResult.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL)) - { - throw OrthancException(ErrorCode_InternalError); - } - - const std::string tmp = findResult.GetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL).GetContent(); - ResourceType level = StringToResourceType(tmp.c_str()); - - Move(targetAet, level, findResult); - } - - - void DicomControlUserConnection::MovePatient(const std::string& targetAet, - const std::string& patientId) - { - DicomMap query; - query.SetValue(DICOM_TAG_PATIENT_ID, patientId, false); - MoveInternal(targetAet, ResourceType_Patient, query); - } - - - void DicomControlUserConnection::MoveStudy(const std::string& targetAet, - const std::string& studyUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - MoveInternal(targetAet, ResourceType_Study, query); - } - - - void DicomControlUserConnection::MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - MoveInternal(targetAet, ResourceType_Series, query); - } - - - void DicomControlUserConnection::MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid) - { - DicomMap query; - query.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, studyUid, false); - query.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid, false); - query.SetValue(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, false); - MoveInternal(targetAet, ResourceType_Instance, query); - } - - - void DicomControlUserConnection::FindWorklist(DicomFindAnswers& result, - ParsedDicomFile& query) - { - DcmDataset* dataset = query.GetDcmtkObject().getDataset(); - const char* sopClass = UID_FINDModalityWorklistInformationModel; - - FindInternal(result, dataset, sopClass, true, NULL); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomControlUserConnection.h --- a/Core/DicomNetworking/DicomControlUserConnection.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 -# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 -#endif - -#include "DicomAssociationParameters.h" -#include "DicomFindAnswers.h" - -#include - -namespace Orthanc -{ - class DicomAssociation; // Forward declaration for PImpl design pattern - - class DicomControlUserConnection : public boost::noncopyable - { - private: - DicomAssociationParameters parameters_; - boost::shared_ptr association_; - - void SetupPresentationContexts(); - - void FindInternal(DicomFindAnswers& answers, - DcmDataset* dataset, - const char* sopClass, - bool isWorklist, - const char* level); - - void MoveInternal(const std::string& targetAet, - ResourceType level, - const DicomMap& fields); - - public: - DicomControlUserConnection(const DicomAssociationParameters& params); - - const DicomAssociationParameters& GetParameters() const - { - return parameters_; - } - - void Close(); - - bool Echo(); - - void Find(DicomFindAnswers& result, - ResourceType level, - const DicomMap& originalFields, - bool normalize); - - void Move(const std::string& targetAet, - ResourceType level, - const DicomMap& findResult); - - void Move(const std::string& targetAet, - const DicomMap& findResult); - - void MovePatient(const std::string& targetAet, - const std::string& patientId); - - void MoveStudy(const std::string& targetAet, - const std::string& studyUid); - - void MoveSeries(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid); - - void MoveInstance(const std::string& targetAet, - const std::string& studyUid, - const std::string& seriesUid, - const std::string& instanceUid); - - void FindWorklist(DicomFindAnswers& result, - ParsedDicomFile& query); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomFindAnswers.cpp --- a/Core/DicomNetworking/DicomFindAnswers.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,210 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomFindAnswers.h" - -#include "../DicomParsing/FromDcmtkBridge.h" -#include "../OrthancException.h" - -#include -#include -#include - - -namespace Orthanc -{ - void DicomFindAnswers::AddAnswerInternal(ParsedDicomFile* answer) - { - std::unique_ptr protection(answer); - - if (isWorklist_) - { - // These lines are necessary when serving worklists, otherwise - // Orthanc does not behave as "wlmscpfs" - protection->Remove(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID); - protection->Remove(DICOM_TAG_SOP_INSTANCE_UID); - } - - protection->ChangeEncoding(encoding_); - - answers_.push_back(protection.release()); - } - - - DicomFindAnswers::DicomFindAnswers(bool isWorklist) : - encoding_(GetDefaultDicomEncoding()), - isWorklist_(isWorklist), - complete_(true) - { - } - - - void DicomFindAnswers::SetEncoding(Encoding encoding) - { - for (size_t i = 0; i < answers_.size(); i++) - { - assert(answers_[i] != NULL); - answers_[i]->ChangeEncoding(encoding); - } - - encoding_ = encoding; - } - - - void DicomFindAnswers::SetWorklist(bool isWorklist) - { - if (answers_.empty()) - { - isWorklist_ = isWorklist; - } - else - { - // This set of answers is not empty anymore, cannot change its type - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - void DicomFindAnswers::Clear() - { - for (size_t i = 0; i < answers_.size(); i++) - { - assert(answers_[i] != NULL); - delete answers_[i]; - } - - answers_.clear(); - } - - - void DicomFindAnswers::Reserve(size_t size) - { - if (size > answers_.size()) - { - answers_.reserve(size); - } - } - - - void DicomFindAnswers::Add(const DicomMap& map) - { - // We use the permissive mode to be tolerant wrt. invalid DICOM - // files that contain some tags with out-of-range values (such - // tags are removed from the answers) - AddAnswerInternal(new ParsedDicomFile(map, encoding_, true /* permissive */)); - //"" /* no private creator */)); - } - - - void DicomFindAnswers::Add(ParsedDicomFile& dicom) - { - AddAnswerInternal(dicom.Clone(true)); - } - - void DicomFindAnswers::Add(const void* dicom, - size_t size) - { - AddAnswerInternal(new ParsedDicomFile(dicom, size)); - } - - - ParsedDicomFile& DicomFindAnswers::GetAnswer(size_t index) const - { - if (index < answers_.size()) - { - return *answers_[index]; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - DcmDataset* DicomFindAnswers::ExtractDcmDataset(size_t index) const - { - // As "DicomFindAnswers" stores its content using class - // "ParsedDicomFile" (that internally uses "DcmFileFormat" from - // DCMTK), the dataset can contain tags that are reserved if - // storing the media on the disk, notably tag - // "MediaStorageSOPClassUID" (0002,0002). In this function, we - // remove all those tags whose group is below 0x0008. The - // resulting data set is clean for emission in the C-FIND SCP. - - // http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#sect_C.4.1.1.3 - // https://groups.google.com/d/msg/orthanc-users/D3kpPuX8yV0/_zgHOzkMEQAJ - - DcmDataset& source = *GetAnswer(index).GetDcmtkObject().getDataset(); - - std::unique_ptr target(new DcmDataset); - - for (unsigned long i = 0; i < source.card(); i++) - { - const DcmElement* element = source.getElement(i); - assert(element != NULL); - - if (element != NULL && - element->getTag().getGroup() >= 0x0008 && - element->getTag().getElement() != 0x0000) - { - target->insert(dynamic_cast(element->clone())); - } - } - - return target.release(); - } - - - void DicomFindAnswers::ToJson(Json::Value& target, - size_t index, - bool simplify) const - { - DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full); - GetAnswer(index).DatasetToJson(target, format, DicomToJsonFlags_None, 0); - } - - - void DicomFindAnswers::ToJson(Json::Value& target, - bool simplify) const - { - target = Json::arrayValue; - - for (size_t i = 0; i < GetSize(); i++) - { - Json::Value answer; - ToJson(answer, i, simplify); - target.append(answer); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomFindAnswers.h --- a/Core/DicomNetworking/DicomFindAnswers.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../DicomParsing/ParsedDicomFile.h" - -namespace Orthanc -{ - class DicomFindAnswers : public boost::noncopyable - { - private: - Encoding encoding_; - bool isWorklist_; - std::vector answers_; - bool complete_; - - void AddAnswerInternal(ParsedDicomFile* answer); - - public: - DicomFindAnswers(bool isWorklist); - - ~DicomFindAnswers() - { - Clear(); - } - - Encoding GetEncoding() const - { - return encoding_; - } - - void SetEncoding(Encoding encoding); - - void SetWorklist(bool isWorklist); - - bool IsWorklist() const - { - return isWorklist_; - } - - void Clear(); - - void Reserve(size_t index); - - void Add(const DicomMap& map); - - void Add(ParsedDicomFile& dicom); - - void Add(const void* dicom, - size_t size); - - size_t GetSize() const - { - return answers_.size(); - } - - ParsedDicomFile& GetAnswer(size_t index) const; - - DcmDataset* ExtractDcmDataset(size_t index) const; - - void ToJson(Json::Value& target, - bool simplify) const; - - void ToJson(Json::Value& target, - size_t index, - bool simplify) const; - - bool IsComplete() const - { - return complete_; - } - - void SetComplete(bool isComplete) - { - complete_ = isComplete; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomServer.cpp --- a/Core/DicomNetworking/DicomServer.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,429 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomServer.h" - -#include "../Logging.h" -#include "../MultiThreading/RunnableWorkersPool.h" -#include "../OrthancException.h" -#include "../Toolbox.h" -#include "Internals/CommandDispatcher.h" - -#include - -#if defined(__linux__) -#include -#endif - - -namespace Orthanc -{ - struct DicomServer::PImpl - { - boost::thread thread_; - T_ASC_Network *network_; - std::unique_ptr workers_; - }; - - - void DicomServer::ServerThread(DicomServer* server) - { - LOG(INFO) << "DICOM server started"; - - while (server->continue_) - { - /* receive an association and acknowledge or reject it. If the association was */ - /* acknowledged, offer corresponding services and invoke one or more if required. */ - std::unique_ptr dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_)); - - try - { - if (dispatcher.get() != NULL) - { - server->pimpl_->workers_->Add(dispatcher.release()); - } - } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception in the DICOM server thread: " << e.What(); - } - } - - LOG(INFO) << "DICOM server stopping"; - } - - - DicomServer::DicomServer() : - pimpl_(new PImpl), - aet_("ANY-SCP") - { - port_ = 104; - modalities_ = NULL; - findRequestHandlerFactory_ = NULL; - moveRequestHandlerFactory_ = NULL; - getRequestHandlerFactory_ = NULL; - storeRequestHandlerFactory_ = NULL; - worklistRequestHandlerFactory_ = NULL; - storageCommitmentFactory_ = NULL; - applicationEntityFilter_ = NULL; - checkCalledAet_ = true; - associationTimeout_ = 30; - continue_ = false; - } - - DicomServer::~DicomServer() - { - if (continue_) - { - LOG(ERROR) << "INTERNAL ERROR: DicomServer::Stop() should be invoked manually to avoid mess in the destruction order!"; - Stop(); - } - } - - void DicomServer::SetPortNumber(uint16_t port) - { - Stop(); - port_ = port; - } - - uint16_t DicomServer::GetPortNumber() const - { - return port_; - } - - void DicomServer::SetAssociationTimeout(uint32_t seconds) - { - LOG(INFO) << "Setting timeout for DICOM connections if Orthanc acts as SCP (server): " - << seconds << " seconds (0 = no timeout)"; - - Stop(); - associationTimeout_ = seconds; - } - - uint32_t DicomServer::GetAssociationTimeout() const - { - return associationTimeout_; - } - - - void DicomServer::SetCalledApplicationEntityTitleCheck(bool check) - { - Stop(); - checkCalledAet_ = check; - } - - bool DicomServer::HasCalledApplicationEntityTitleCheck() const - { - return checkCalledAet_; - } - - void DicomServer::SetApplicationEntityTitle(const std::string& aet) - { - if (aet.size() == 0) - { - throw OrthancException(ErrorCode_BadApplicationEntityTitle); - } - - if (aet.size() > 16) - { - throw OrthancException(ErrorCode_BadApplicationEntityTitle); - } - - for (size_t i = 0; i < aet.size(); i++) - { - if (!(aet[i] == '-' || - aet[i] == '_' || - isdigit(aet[i]) || - (aet[i] >= 'A' && aet[i] <= 'Z'))) - { - LOG(WARNING) << "For best interoperability, only upper case, alphanumeric characters should be present in AET: \"" << aet << "\""; - break; - } - } - - Stop(); - aet_ = aet; - } - - const std::string& DicomServer::GetApplicationEntityTitle() const - { - return aet_; - } - - void DicomServer::SetRemoteModalities(IRemoteModalities& modalities) - { - Stop(); - modalities_ = &modalities; - } - - DicomServer::IRemoteModalities& DicomServer::GetRemoteModalities() const - { - if (modalities_ == NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return *modalities_; - } - } - - void DicomServer::SetFindRequestHandlerFactory(IFindRequestHandlerFactory& factory) - { - Stop(); - findRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasFindRequestHandlerFactory() const - { - return (findRequestHandlerFactory_ != NULL); - } - - IFindRequestHandlerFactory& DicomServer::GetFindRequestHandlerFactory() const - { - if (HasFindRequestHandlerFactory()) - { - return *findRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCFindHandler); - } - } - - void DicomServer::SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& factory) - { - Stop(); - moveRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasMoveRequestHandlerFactory() const - { - return (moveRequestHandlerFactory_ != NULL); - } - - IMoveRequestHandlerFactory& DicomServer::GetMoveRequestHandlerFactory() const - { - if (HasMoveRequestHandlerFactory()) - { - return *moveRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCMoveHandler); - } - } - - void DicomServer::SetGetRequestHandlerFactory(IGetRequestHandlerFactory& factory) - { - Stop(); - getRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasGetRequestHandlerFactory() const - { - return (getRequestHandlerFactory_ != NULL); - } - - IGetRequestHandlerFactory& DicomServer::GetGetRequestHandlerFactory() const - { - if (HasGetRequestHandlerFactory()) - { - return *getRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCGetHandler); - } - } - - void DicomServer::SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& factory) - { - Stop(); - storeRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasStoreRequestHandlerFactory() const - { - return (storeRequestHandlerFactory_ != NULL); - } - - IStoreRequestHandlerFactory& DicomServer::GetStoreRequestHandlerFactory() const - { - if (HasStoreRequestHandlerFactory()) - { - return *storeRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoCStoreHandler); - } - } - - void DicomServer::SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& factory) - { - Stop(); - worklistRequestHandlerFactory_ = &factory; - } - - bool DicomServer::HasWorklistRequestHandlerFactory() const - { - return (worklistRequestHandlerFactory_ != NULL); - } - - IWorklistRequestHandlerFactory& DicomServer::GetWorklistRequestHandlerFactory() const - { - if (HasWorklistRequestHandlerFactory()) - { - return *worklistRequestHandlerFactory_; - } - else - { - throw OrthancException(ErrorCode_NoWorklistHandler); - } - } - - void DicomServer::SetStorageCommitmentRequestHandlerFactory(IStorageCommitmentRequestHandlerFactory& factory) - { - Stop(); - storageCommitmentFactory_ = &factory; - } - - bool DicomServer::HasStorageCommitmentRequestHandlerFactory() const - { - return (storageCommitmentFactory_ != NULL); - } - - IStorageCommitmentRequestHandlerFactory& DicomServer::GetStorageCommitmentRequestHandlerFactory() const - { - if (HasStorageCommitmentRequestHandlerFactory()) - { - return *storageCommitmentFactory_; - } - else - { - throw OrthancException(ErrorCode_NoStorageCommitmentHandler); - } - } - - void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory) - { - Stop(); - applicationEntityFilter_ = &factory; - } - - bool DicomServer::HasApplicationEntityFilter() const - { - return (applicationEntityFilter_ != NULL); - } - - IApplicationEntityFilter& DicomServer::GetApplicationEntityFilter() const - { - if (HasApplicationEntityFilter()) - { - return *applicationEntityFilter_; - } - else - { - throw OrthancException(ErrorCode_NoApplicationEntityFilter); - } - } - - void DicomServer::Start() - { - if (modalities_ == NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "No list of modalities was provided to the DICOM server"); - } - - Stop(); - - /* initialize network, i.e. create an instance of T_ASC_Network*. */ - OFCondition cond = ASC_initializeNetwork - (NET_ACCEPTOR, OFstatic_cast(int, port_), /*opt_acse_timeout*/ 30, &pimpl_->network_); - if (cond.bad()) - { - throw OrthancException(ErrorCode_DicomPortInUse, - " (port = " + boost::lexical_cast(port_) + ") cannot create network: " + std::string(cond.text())); - } - - continue_ = true; - pimpl_->workers_.reset(new RunnableWorkersPool(4)); // Use 4 workers - TODO as a parameter? - pimpl_->thread_ = boost::thread(ServerThread, this); - } - - - void DicomServer::Stop() - { - if (continue_) - { - continue_ = false; - - if (pimpl_->thread_.joinable()) - { - pimpl_->thread_.join(); - } - - pimpl_->workers_.reset(NULL); - - /* drop the network, i.e. free memory of T_ASC_Network* structure. This call */ - /* is the counterpart of ASC_initializeNetwork(...) which was called above. */ - OFCondition cond = ASC_dropNetwork(&pimpl_->network_); - if (cond.bad()) - { - LOG(ERROR) << "Error while dropping the network: " << cond.text(); - } - } - } - - - bool DicomServer::IsMyAETitle(const std::string& aet) const - { - if (modalities_ == NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - if (!HasCalledApplicationEntityTitleCheck()) - { - // OK, no check on the AET. - return true; - } - else - { - return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle()); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomServer.h --- a/Core/DicomNetworking/DicomServer.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 -# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 -#endif - -#include "IFindRequestHandlerFactory.h" -#include "IMoveRequestHandlerFactory.h" -#include "IGetRequestHandlerFactory.h" -#include "IStoreRequestHandlerFactory.h" -#include "IWorklistRequestHandlerFactory.h" -#include "IStorageCommitmentRequestHandlerFactory.h" -#include "IApplicationEntityFilter.h" -#include "RemoteModalityParameters.h" - -#include -#include - - -namespace Orthanc -{ - class DicomServer : public boost::noncopyable - { - public: - // WARNING: The methods of this class must be thread-safe - class IRemoteModalities : public boost::noncopyable - { - public: - virtual ~IRemoteModalities() - { - } - - virtual bool IsSameAETitle(const std::string& aet1, - const std::string& aet2) = 0; - - virtual bool LookupAETitle(RemoteModalityParameters& modality, - const std::string& aet) = 0; - }; - - private: - struct PImpl; - boost::shared_ptr pimpl_; - - bool checkCalledAet_; - std::string aet_; - uint16_t port_; - bool continue_; - uint32_t associationTimeout_; - IRemoteModalities* modalities_; - IFindRequestHandlerFactory* findRequestHandlerFactory_; - IMoveRequestHandlerFactory* moveRequestHandlerFactory_; - IGetRequestHandlerFactory* getRequestHandlerFactory_; - IStoreRequestHandlerFactory* storeRequestHandlerFactory_; - IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_; - IStorageCommitmentRequestHandlerFactory* storageCommitmentFactory_; - IApplicationEntityFilter* applicationEntityFilter_; - - static void ServerThread(DicomServer* server); - - public: - DicomServer(); - - ~DicomServer(); - - void SetPortNumber(uint16_t port); - uint16_t GetPortNumber() const; - - void SetAssociationTimeout(uint32_t seconds); - uint32_t GetAssociationTimeout() const; - - void SetCalledApplicationEntityTitleCheck(bool check); - bool HasCalledApplicationEntityTitleCheck() const; - - void SetApplicationEntityTitle(const std::string& aet); - const std::string& GetApplicationEntityTitle() const; - - void SetRemoteModalities(IRemoteModalities& modalities); - IRemoteModalities& GetRemoteModalities() const; - - void SetFindRequestHandlerFactory(IFindRequestHandlerFactory& handler); - bool HasFindRequestHandlerFactory() const; - IFindRequestHandlerFactory& GetFindRequestHandlerFactory() const; - - void SetMoveRequestHandlerFactory(IMoveRequestHandlerFactory& handler); - bool HasMoveRequestHandlerFactory() const; - IMoveRequestHandlerFactory& GetMoveRequestHandlerFactory() const; - - void SetGetRequestHandlerFactory(IGetRequestHandlerFactory& handler); - bool HasGetRequestHandlerFactory() const; - IGetRequestHandlerFactory& GetGetRequestHandlerFactory() const; - - void SetStoreRequestHandlerFactory(IStoreRequestHandlerFactory& handler); - bool HasStoreRequestHandlerFactory() const; - IStoreRequestHandlerFactory& GetStoreRequestHandlerFactory() const; - - void SetWorklistRequestHandlerFactory(IWorklistRequestHandlerFactory& handler); - bool HasWorklistRequestHandlerFactory() const; - IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const; - - void SetStorageCommitmentRequestHandlerFactory(IStorageCommitmentRequestHandlerFactory& handler); - bool HasStorageCommitmentRequestHandlerFactory() const; - IStorageCommitmentRequestHandlerFactory& GetStorageCommitmentRequestHandlerFactory() const; - - void SetApplicationEntityFilter(IApplicationEntityFilter& handler); - bool HasApplicationEntityFilter() const; - IApplicationEntityFilter& GetApplicationEntityFilter() const; - - void Start(); - - void Stop(); - - bool IsMyAETitle(const std::string& aet) const; - }; - -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomStoreUserConnection.cpp --- a/Core/DicomNetworking/DicomStoreUserConnection.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,527 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomStoreUserConnection.h" - -#include "../DicomParsing/FromDcmtkBridge.h" -#include "../DicomParsing/ParsedDicomFile.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "DicomAssociation.h" - -#include - - -namespace Orthanc -{ - bool DicomStoreUserConnection::ProposeStorageClass(const std::string& sopClassUid, - const std::set& syntaxes) - { - // Default transfer syntax for DICOM - const bool addLittleEndianImplicit = ( - proposeUncompressedSyntaxes_ && - syntaxes.find(DicomTransferSyntax_LittleEndianImplicit) == syntaxes.end()); - - const bool addLittleEndianExplicit = ( - proposeUncompressedSyntaxes_ && - syntaxes.find(DicomTransferSyntax_LittleEndianExplicit) == syntaxes.end()); - - const bool addBigEndianExplicit = ( - proposeUncompressedSyntaxes_ && - proposeRetiredBigEndian_ && - syntaxes.find(DicomTransferSyntax_BigEndianExplicit) == syntaxes.end()); - - size_t requiredCount = syntaxes.size(); - if (addLittleEndianImplicit) - { - requiredCount += 1; - } - - if (addLittleEndianExplicit || - addBigEndianExplicit) - { - requiredCount += 1; - } - - if (association_->GetRemainingPropositions() <= requiredCount) - { - return false; // Not enough room - } - else - { - for (std::set::const_iterator - it = syntaxes.begin(); it != syntaxes.end(); ++it) - { - association_->ProposePresentationContext(sopClassUid, *it); - proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *it)); - } - - if (addLittleEndianImplicit) - { - association_->ProposePresentationContext(sopClassUid, DicomTransferSyntax_LittleEndianImplicit); - proposedOriginalClasses_.insert(std::make_pair(sopClassUid, DicomTransferSyntax_LittleEndianImplicit)); - } - - if (addLittleEndianExplicit || - addBigEndianExplicit) - { - std::set uncompressed; - - if (addLittleEndianExplicit) - { - uncompressed.insert(DicomTransferSyntax_LittleEndianExplicit); - } - - if (addBigEndianExplicit) - { - uncompressed.insert(DicomTransferSyntax_BigEndianExplicit); - } - - association_->ProposePresentationContext(sopClassUid, uncompressed); - - assert(!uncompressed.empty()); - if (addLittleEndianExplicit ^ addBigEndianExplicit) - { - // Only one transfer syntax was proposed for this presentation context - assert(uncompressed.size() == 1); - proposedOriginalClasses_.insert(std::make_pair(sopClassUid, *uncompressed.begin())); - } - } - - return true; - } - } - - - bool DicomStoreUserConnection::LookupPresentationContext( - uint8_t& presentationContextId, - const std::string& sopClassUid, - DicomTransferSyntax transferSyntax) - { - typedef std::map PresentationContexts; - - PresentationContexts pc; - if (association_->IsOpen() && - association_->LookupAcceptedPresentationContext(pc, sopClassUid)) - { - PresentationContexts::const_iterator found = pc.find(transferSyntax); - if (found != pc.end()) - { - presentationContextId = found->second; - return true; - } - } - - return false; - } - - - DicomStoreUserConnection::DicomStoreUserConnection( - const DicomAssociationParameters& params) : - parameters_(params), - association_(new DicomAssociation), - proposeCommonClasses_(true), - proposeUncompressedSyntaxes_(true), - proposeRetiredBigEndian_(false) - { - } - - - void DicomStoreUserConnection::RegisterStorageClass(const std::string& sopClassUid, - DicomTransferSyntax syntax) - { - RegisteredClasses::iterator found = registeredClasses_.find(sopClassUid); - - if (found == registeredClasses_.end()) - { - std::set ts; - ts.insert(syntax); - registeredClasses_[sopClassUid] = ts; - } - else - { - found->second.insert(syntax); - } - } - - - void DicomStoreUserConnection::LookupParameters(std::string& sopClassUid, - std::string& sopInstanceUid, - DicomTransferSyntax& transferSyntax, - DcmFileFormat& dicom) - { - if (dicom.getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - OFString a, b; - if (!dicom.getDataset()->findAndGetOFString(DCM_SOPClassUID, a).good() || - !dicom.getDataset()->findAndGetOFString(DCM_SOPInstanceUID, b).good()) - { - throw OrthancException(ErrorCode_NoSopClassOrInstance, - "Unable to determine the SOP class/instance for C-STORE with AET " + - parameters_.GetRemoteModality().GetApplicationEntityTitle()); - } - - sopClassUid.assign(a.c_str()); - sopInstanceUid.assign(b.c_str()); - - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax, dicom)) - { - throw OrthancException(ErrorCode_InternalError, - "Unknown transfer syntax from DCMTK"); - } - } - - - bool DicomStoreUserConnection::NegotiatePresentationContext( - uint8_t& presentationContextId, - const std::string& sopClassUid, - DicomTransferSyntax transferSyntax) - { - /** - * Step 1: Check whether this presentation context is already - * available in the previously negotiated assocation. - **/ - - if (LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax)) - { - return true; - } - - // The association must be re-negotiated - if (association_->IsOpen()) - { - LOG(INFO) << "Re-negotiating DICOM association with " - << parameters_.GetRemoteModality().GetApplicationEntityTitle(); - - if (proposedOriginalClasses_.find(std::make_pair(sopClassUid, transferSyntax)) != - proposedOriginalClasses_.end()) - { - LOG(INFO) << "The remote modality has already rejected SOP class UID \"" - << sopClassUid << "\" with transfer syntax \"" - << GetTransferSyntaxUid(transferSyntax) << "\", don't renegotiate"; - return false; - } - } - - association_->ClearPresentationContexts(); - proposedOriginalClasses_.clear(); - RegisterStorageClass(sopClassUid, transferSyntax); // (*) - - - /** - * Step 2: Propose at least the mandatory SOP class. - **/ - - { - RegisteredClasses::const_iterator mandatory = registeredClasses_.find(sopClassUid); - - if (mandatory == registeredClasses_.end() || - mandatory->second.find(transferSyntax) == mandatory->second.end()) - { - // Should never fail because of (*) - throw OrthancException(ErrorCode_InternalError); - } - - if (!ProposeStorageClass(sopClassUid, mandatory->second)) - { - // Should never happen in real life: There are no more than - // 128 transfer syntaxes in DICOM! - throw OrthancException(ErrorCode_InternalError, - "Too many transfer syntaxes for SOP class UID: " + sopClassUid); - } - } - - - /** - * Step 3: Propose all the previously spotted SOP classes, as - * registered through the "RegisterStorageClass()" method. - **/ - - for (RegisteredClasses::const_iterator it = registeredClasses_.begin(); - it != registeredClasses_.end(); ++it) - { - if (it->first != sopClassUid) - { - ProposeStorageClass(it->first, it->second); - } - } - - - /** - * Step 4: As long as there is room left in the proposed - * presentation contexts, propose the uncompressed transfer syntaxes - * for the most common SOP classes, as can be found in the - * "dcmShortSCUStorageSOPClassUIDs" array from DCMTK. The - * preferred transfer syntax is "LittleEndianImplicit". - **/ - - if (proposeCommonClasses_) - { - // The method "ProposeStorageClass()" will automatically add - // "LittleEndianImplicit" - std::set ts; - - for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs; i++) - { - std::string c(dcmShortSCUStorageSOPClassUIDs[i]); - - if (c != sopClassUid && - registeredClasses_.find(c) == registeredClasses_.end()) - { - ProposeStorageClass(c, ts); - } - } - } - - - /** - * Step 5: Open the association, and check whether the pair (SOP - * class UID, transfer syntax) was accepted by the remote host. - **/ - - association_->Open(parameters_); - return LookupPresentationContext(presentationContextId, sopClassUid, transferSyntax); - } - - - void DicomStoreUserConnection::Store(std::string& sopClassUid, - std::string& sopInstanceUid, - DcmFileFormat& dicom, - bool hasMoveOriginator, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - DicomTransferSyntax transferSyntax; - LookupParameters(sopClassUid, sopInstanceUid, transferSyntax, dicom); - - uint8_t presID; - if (!NegotiatePresentationContext(presID, sopClassUid, transferSyntax)) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "No valid presentation context was negotiated for " - "SOP class UID [" + sopClassUid + "] and transfer " - "syntax [" + GetTransferSyntaxUid(transferSyntax) + "] " - "while sending to modality [" + - parameters_.GetRemoteModality().GetApplicationEntityTitle() + "]"); - } - - // Prepare the transmission of data - T_DIMSE_C_StoreRQ request; - memset(&request, 0, sizeof(request)); - request.MessageID = association_->GetDcmtkAssociation().nextMsgID++; - strncpy(request.AffectedSOPClassUID, sopClassUid.c_str(), DIC_UI_LEN); - request.Priority = DIMSE_PRIORITY_MEDIUM; - request.DataSetType = DIMSE_DATASET_PRESENT; - strncpy(request.AffectedSOPInstanceUID, sopInstanceUid.c_str(), DIC_UI_LEN); - - if (hasMoveOriginator) - { - strncpy(request.MoveOriginatorApplicationEntityTitle, - moveOriginatorAET.c_str(), DIC_AE_LEN); - request.opts = O_STORE_MOVEORIGINATORAETITLE; - - request.MoveOriginatorID = moveOriginatorID; // The type DIC_US is an alias for uint16_t - request.opts |= O_STORE_MOVEORIGINATORID; - } - - if (dicom.getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - // Finally conduct transmission of data - T_DIMSE_C_StoreRSP response; - DcmDataset* statusDetail = NULL; - DicomAssociation::CheckCondition( - DIMSE_storeUser(&association_->GetDcmtkAssociation(), presID, &request, - NULL, dicom.getDataset(), /*progressCallback*/ NULL, NULL, - /*opt_blockMode*/ (GetParameters().HasTimeout() ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ GetParameters().GetTimeout(), - &response, &statusDetail, NULL), - GetParameters(), "C-STORE"); - - if (statusDetail != NULL) - { - delete statusDetail; - } - - /** - * New in Orthanc 1.6.0: Deal with failures during C-STORE. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.2.3.html#table_B.2-1 - **/ - - if (response.DimseStatus != 0x0000 && // Success - response.DimseStatus != 0xB000 && // Warning - Coercion of Data Elements - response.DimseStatus != 0xB007 && // Warning - Data Set does not match SOP Class - response.DimseStatus != 0xB006) // Warning - Elements Discarded - { - char buf[16]; - sprintf(buf, "%04X", response.DimseStatus); - throw OrthancException(ErrorCode_NetworkProtocol, - "C-STORE SCU to AET \"" + - GetParameters().GetRemoteModality().GetApplicationEntityTitle() + - "\" has failed with DIMSE status 0x" + buf); - } - } - - - void DicomStoreUserConnection::Store(std::string& sopClassUid, - std::string& sopInstanceUid, - const void* buffer, - size_t size, - bool hasMoveOriginator, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - std::unique_ptr dicom( - FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); - - if (dicom.get() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - Store(sopClassUid, sopInstanceUid, *dicom, hasMoveOriginator, moveOriginatorAET, moveOriginatorID); - } - - - void DicomStoreUserConnection::LookupTranscoding(std::set& acceptedSyntaxes, - const std::string& sopClassUid, - DicomTransferSyntax sourceSyntax) - { - acceptedSyntaxes.clear(); - - // Make sure a negotiation has already occurred for this transfer - // syntax. We don't use the return code: Transcoding is possible - // even if the "sourceSyntax" is not supported. - uint8_t presID; - NegotiatePresentationContext(presID, sopClassUid, sourceSyntax); - - std::map contexts; - if (association_->LookupAcceptedPresentationContext(contexts, sopClassUid)) - { - for (std::map::const_iterator - it = contexts.begin(); it != contexts.end(); ++it) - { - acceptedSyntaxes.insert(it->first); - } - } - } - - - void DicomStoreUserConnection::Transcode(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - IDicomTranscoder& transcoder, - const void* buffer, - size_t size, - bool hasMoveOriginator, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID) - { - std::unique_ptr dicom(FromDcmtkBridge::LoadFromMemoryBuffer(buffer, size)); - if (dicom.get() == NULL || - dicom->getDataset() == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - - DicomTransferSyntax inputSyntax; - LookupParameters(sopClassUid, sopInstanceUid, inputSyntax, *dicom); - - std::set accepted; - LookupTranscoding(accepted, sopClassUid, inputSyntax); - - if (accepted.find(inputSyntax) != accepted.end()) - { - // No need for transcoding - Store(sopClassUid, sopInstanceUid, *dicom, - hasMoveOriginator, moveOriginatorAET, moveOriginatorID); - } - else - { - // Transcoding is needed - std::set uncompressedSyntaxes; - - if (accepted.find(DicomTransferSyntax_LittleEndianImplicit) != accepted.end()) - { - uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianImplicit); - } - - if (accepted.find(DicomTransferSyntax_LittleEndianExplicit) != accepted.end()) - { - uncompressedSyntaxes.insert(DicomTransferSyntax_LittleEndianExplicit); - } - - if (accepted.find(DicomTransferSyntax_BigEndianExplicit) != accepted.end()) - { - uncompressedSyntaxes.insert(DicomTransferSyntax_BigEndianExplicit); - } - - IDicomTranscoder::DicomImage source; - source.AcquireParsed(dicom.release()); - source.SetExternalBuffer(buffer, size); - - const std::string sourceUid = IDicomTranscoder::GetSopInstanceUid(source.GetParsed()); - - IDicomTranscoder::DicomImage transcoded; - if (transcoder.Transcode(transcoded, source, uncompressedSyntaxes, false)) - { - if (sourceUid != IDicomTranscoder::GetSopInstanceUid(transcoded.GetParsed())) - { - throw OrthancException(ErrorCode_Plugin, "The transcoder has changed the SOP " - "instance UID while transcoding to an uncompressed transfer syntax"); - } - else - { - DicomTransferSyntax transcodedSyntax; - - // Sanity check - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transcodedSyntax, transcoded.GetParsed()) || - accepted.find(transcodedSyntax) == accepted.end()) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - Store(sopClassUid, sopInstanceUid, transcoded.GetParsed(), - hasMoveOriginator, moveOriginatorAET, moveOriginatorID); - } - } - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/DicomStoreUserConnection.h --- a/Core/DicomNetworking/DicomStoreUserConnection.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,179 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING) -# error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file -#endif - -#include "DicomAssociationParameters.h" - -#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 -# include "../DicomParsing/IDicomTranscoder.h" -#endif - -#include -#include -#include -#include // For uint8_t - - -class DcmFileFormat; - -namespace Orthanc -{ - /** - - Orthanc < 1.7.0: - - Input | Output - -------------+--------------------------------------------- - Compressed | Same transfer syntax - Uncompressed | Same transfer syntax, or other uncompressed - - Orthanc >= 1.7.0: - - Input | Output - -------------+--------------------------------------------- - Compressed | Same transfer syntax, or uncompressed - Uncompressed | Same transfer syntax, or other uncompressed - - **/ - - class DicomAssociation; // Forward declaration for PImpl design pattern - - class DicomStoreUserConnection : public boost::noncopyable - { - private: - typedef std::map > RegisteredClasses; - - // "ProposedOriginalClasses" keeps track of the storage classes - // that were proposed with a single transfer syntax - typedef std::set< std::pair > ProposedOriginalClasses; - - DicomAssociationParameters parameters_; - boost::shared_ptr association_; // "shared_ptr" is for PImpl - RegisteredClasses registeredClasses_; - ProposedOriginalClasses proposedOriginalClasses_; - bool proposeCommonClasses_; - bool proposeUncompressedSyntaxes_; - bool proposeRetiredBigEndian_; - - // Return "false" if there is not enough room remaining in the association - bool ProposeStorageClass(const std::string& sopClassUid, - const std::set& syntaxes); - - bool LookupPresentationContext(uint8_t& presentationContextId, - const std::string& sopClassUid, - DicomTransferSyntax transferSyntax); - - bool NegotiatePresentationContext(uint8_t& presentationContextId, - const std::string& sopClassUid, - DicomTransferSyntax transferSyntax); - - void LookupTranscoding(std::set& acceptedSyntaxes, - const std::string& sopClassUid, - DicomTransferSyntax sourceSyntax); - - public: - DicomStoreUserConnection(const DicomAssociationParameters& params); - - const DicomAssociationParameters& GetParameters() const - { - return parameters_; - } - - void SetCommonClassesProposed(bool proposed) - { - proposeCommonClasses_ = proposed; - } - - bool IsCommonClassesProposed() const - { - return proposeCommonClasses_; - } - - void SetUncompressedSyntaxesProposed(bool proposed) - { - proposeUncompressedSyntaxes_ = proposed; - } - - bool IsUncompressedSyntaxesProposed() const - { - return proposeUncompressedSyntaxes_; - } - - void SetRetiredBigEndianProposed(bool propose) - { - proposeRetiredBigEndian_ = propose; - } - - bool IsRetiredBigEndianProposed() const - { - return proposeRetiredBigEndian_; - } - - void RegisterStorageClass(const std::string& sopClassUid, - DicomTransferSyntax syntax); - - void Store(std::string& sopClassUid, - std::string& sopInstanceUid, - DcmFileFormat& dicom, - bool hasMoveOriginator, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void Store(std::string& sopClassUid, - std::string& sopInstanceUid, - const void* buffer, - size_t size, - bool hasMoveOriginator, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - - void LookupParameters(std::string& sopClassUid, - std::string& sopInstanceUid, - DicomTransferSyntax& transferSyntax, - DcmFileFormat& dicom); - - void Transcode(std::string& sopClassUid /* out */, - std::string& sopInstanceUid /* out */, - IDicomTranscoder& transcoder, - const void* buffer, - size_t size, - bool hasMoveOriginator, - const std::string& moveOriginatorAET, - uint16_t moveOriginatorID); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IApplicationEntityFilter.h --- a/Core/DicomNetworking/IApplicationEntityFilter.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include - -namespace Orthanc -{ - class IApplicationEntityFilter : public boost::noncopyable - { - public: - virtual ~IApplicationEntityFilter() - { - } - - virtual bool IsAllowedConnection(const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; - - virtual bool IsAllowedRequest(const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - DicomRequestType type) = 0; - - virtual bool IsAllowedTransferSyntax(const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - TransferSyntax syntax) = 0; - - virtual bool IsUnknownSopClassAccepted(const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IFindRequestHandler.h --- a/Core/DicomNetworking/IFindRequestHandler.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DicomFindAnswers.h" - -#include - -namespace Orthanc -{ - class IFindRequestHandler : public boost::noncopyable - { - public: - virtual ~IFindRequestHandler() - { - } - - virtual void Handle(DicomFindAnswers& answers, - const DicomMap& input, - const std::list& sequencesToReturn, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IFindRequestHandlerFactory.h --- a/Core/DicomNetworking/IFindRequestHandlerFactory.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IFindRequestHandler.h" - -namespace Orthanc -{ - class IFindRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IFindRequestHandlerFactory() - { - } - - virtual IFindRequestHandler* ConstructFindRequestHandler() = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IGetRequestHandler.h --- a/Core/DicomNetworking/IGetRequestHandler.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include - -#include "../DicomFormat/DicomMap.h" - -#include - - -namespace Orthanc -{ - class IGetRequestHandler : boost::noncopyable - { - public: - enum Status - { - Status_Success, - Status_Failure, - Status_Warning - }; - - virtual ~IGetRequestHandler() - { - } - - virtual bool Handle(const DicomMap& input, - const std::string& originatorIp, - const std::string& originatorAet, - const std::string& calledAet, - uint32_t timeout) = 0; - - virtual unsigned int GetSubOperationCount() const = 0; - - virtual Status DoNext(T_ASC_Association *) = 0; - - virtual unsigned int GetRemainingCount() const = 0; - - virtual unsigned int GetCompletedCount() const = 0; - - virtual unsigned int GetWarningCount() const = 0; - - virtual unsigned int GetFailedCount() const = 0; - - virtual const std::string& GetFailedUids() const = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IGetRequestHandlerFactory.h --- a/Core/DicomNetworking/IGetRequestHandlerFactory.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IGetRequestHandler.h" - -namespace Orthanc -{ - class IGetRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IGetRequestHandlerFactory() - { - } - - virtual IGetRequestHandler* ConstructGetRequestHandler() = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IMoveRequestHandler.h --- a/Core/DicomNetworking/IMoveRequestHandler.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../DicomFormat/DicomMap.h" - -#include -#include - - -namespace Orthanc -{ - class IMoveRequestIterator : public boost::noncopyable - { - public: - enum Status - { - Status_Success, - Status_Failure, - Status_Warning - }; - - virtual ~IMoveRequestIterator() - { - } - - virtual unsigned int GetSubOperationCount() const = 0; - - virtual Status DoNext() = 0; - }; - - - class IMoveRequestHandler - { - public: - virtual ~IMoveRequestHandler() - { - } - - virtual IMoveRequestIterator* Handle(const std::string& targetAet, - const DicomMap& input, - const std::string& originatorIp, - const std::string& originatorAet, - const std::string& calledAet, - uint16_t originatorId) = 0; - }; - -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IMoveRequestHandlerFactory.h --- a/Core/DicomNetworking/IMoveRequestHandlerFactory.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IMoveRequestHandler.h" - -namespace Orthanc -{ - class IMoveRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IMoveRequestHandlerFactory() - { - } - - virtual IMoveRequestHandler* ConstructMoveRequestHandler() = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IStorageCommitmentRequestHandler.h --- a/Core/DicomNetworking/IStorageCommitmentRequestHandler.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include -#include -#include - -namespace Orthanc -{ - class IStorageCommitmentRequestHandler : public boost::noncopyable - { - public: - virtual ~IStorageCommitmentRequestHandler() - { - } - - virtual void HandleRequest(const std::string& transactionUid, - const std::vector& sopClassUids, - const std::vector& sopInstanceUids, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; - - virtual void HandleReport(const std::string& transactionUid, - const std::vector& successSopClassUids, - const std::vector& successSopInstanceUids, - const std::vector& failedSopClassUids, - const std::vector& failedSopInstanceUids, - const std::vector& failureReasons, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IStorageCommitmentRequestHandlerFactory.h --- a/Core/DicomNetworking/IStorageCommitmentRequestHandlerFactory.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IStorageCommitmentRequestHandler.h" - -namespace Orthanc -{ - class IStorageCommitmentRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IStorageCommitmentRequestHandlerFactory() - { - } - - virtual IStorageCommitmentRequestHandler* ConstructStorageCommitmentRequestHandler() = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IStoreRequestHandler.h --- a/Core/DicomNetworking/IStoreRequestHandler.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../DicomFormat/DicomMap.h" - -#include -#include -#include - -namespace Orthanc -{ - class IStoreRequestHandler : public boost::noncopyable - { - public: - virtual ~IStoreRequestHandler() - { - } - - virtual void Handle(const std::string& dicomFile, - const DicomMap& dicomSummary, - const Json::Value& dicomJson, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IStoreRequestHandlerFactory.h --- a/Core/DicomNetworking/IStoreRequestHandlerFactory.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IStoreRequestHandler.h" - -namespace Orthanc -{ - class IStoreRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IStoreRequestHandlerFactory() - { - } - - virtual IStoreRequestHandler* ConstructStoreRequestHandler() = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IWorklistRequestHandler.h --- a/Core/DicomNetworking/IWorklistRequestHandler.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DicomFindAnswers.h" - -namespace Orthanc -{ - class IWorklistRequestHandler : public boost::noncopyable - { - public: - virtual ~IWorklistRequestHandler() - { - } - - virtual void Handle(DicomFindAnswers& answers, - ParsedDicomFile& query, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - ModalityManufacturer manufacturer) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/IWorklistRequestHandlerFactory.h --- a/Core/DicomNetworking/IWorklistRequestHandlerFactory.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IWorklistRequestHandler.h" - -namespace Orthanc -{ - class IWorklistRequestHandlerFactory : public boost::noncopyable - { - public: - virtual ~IWorklistRequestHandlerFactory() - { - } - - virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/CommandDispatcher.cpp --- a/Core/DicomNetworking/Internals/CommandDispatcher.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1346 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../../PrecompiledHeaders.h" -#include "CommandDispatcher.h" - -#if !defined(DCMTK_VERSION_NUMBER) -# error The macro DCMTK_VERSION_NUMBER must be defined -#endif - -#include "FindScp.h" -#include "StoreScp.h" -#include "MoveScp.h" -#include "GetScp.h" -#include "../../Compatibility.h" -#include "../../Toolbox.h" -#include "../../Logging.h" -#include "../../OrthancException.h" - -#include /* for storage commitment */ -#include /* for class DcmSequenceOfItems */ -#include /* for variable dcmAllStorageSOPClassUIDs */ -#include /* for class DcmAssociationConfiguration */ - -#include - - static OFBool opt_rejectWithoutImplementationUID = OFFalse; - - - -static DUL_PRESENTATIONCONTEXT * -findPresentationContextID(LST_HEAD * head, - T_ASC_PresentationContextID presentationContextID) -{ - DUL_PRESENTATIONCONTEXT *pc; - LST_HEAD **l; - OFBool found = OFFalse; - - if (head == NULL) - return NULL; - - l = &head; - if (*l == NULL) - return NULL; - - pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Head(l)); - (void)LST_Position(l, OFstatic_cast(LST_NODE *, pc)); - - while (pc && !found) { - if (pc->presentationContextID == presentationContextID) { - found = OFTrue; - } else { - pc = OFstatic_cast(DUL_PRESENTATIONCONTEXT *, LST_Next(l)); - } - } - return pc; -} - - -/** accept all presenstation contexts for unknown SOP classes, - * i.e. UIDs appearing in the list of abstract syntaxes - * where no corresponding name is defined in the UID dictionary. - * @param params pointer to association parameters structure - * @param transferSyntax transfer syntax to accept - * @param acceptedRole SCU/SCP role to accept - */ -static OFCondition acceptUnknownContextsWithTransferSyntax( - T_ASC_Parameters * params, - const char* transferSyntax, - T_ASC_SC_ROLE acceptedRole) -{ - OFCondition cond = EC_Normal; - int n, i, k; - DUL_PRESENTATIONCONTEXT *dpc; - T_ASC_PresentationContext pc; - OFBool accepted = OFFalse; - OFBool abstractOK = OFFalse; - - n = ASC_countPresentationContexts(params); - for (i = 0; i < n; i++) - { - cond = ASC_getPresentationContext(params, i, &pc); - if (cond.bad()) return cond; - abstractOK = OFFalse; - accepted = OFFalse; - - if (dcmFindNameOfUID(pc.abstractSyntax) == NULL) - { - abstractOK = OFTrue; - - /* check the transfer syntax */ - for (k = 0; (k < OFstatic_cast(int, pc.transferSyntaxCount)) && !accepted; k++) - { - if (strcmp(pc.proposedTransferSyntaxes[k], transferSyntax) == 0) - { - accepted = OFTrue; - } - } - } - - if (accepted) - { - cond = ASC_acceptPresentationContext( - params, pc.presentationContextID, - transferSyntax, acceptedRole); - if (cond.bad()) return cond; - } else { - T_ASC_P_ResultReason reason; - - /* do not refuse if already accepted */ - dpc = findPresentationContextID(params->DULparams.acceptedPresentationContext, - pc.presentationContextID); - if ((dpc == NULL) || ((dpc != NULL) && (dpc->result != ASC_P_ACCEPTANCE))) - { - - if (abstractOK) { - reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; - } else { - reason = ASC_P_ABSTRACTSYNTAXNOTSUPPORTED; - } - /* - * If previously this presentation context was refused - * because of bad transfer syntax let it stay that way. - */ - if ((dpc != NULL) && (dpc->result == ASC_P_TRANSFERSYNTAXESNOTSUPPORTED)) - reason = ASC_P_TRANSFERSYNTAXESNOTSUPPORTED; - - cond = ASC_refusePresentationContext(params, pc.presentationContextID, reason); - if (cond.bad()) return cond; - } - } - } - return EC_Normal; -} - - -/** accept all presenstation contexts for unknown SOP classes, - * i.e. UIDs appearing in the list of abstract syntaxes - * where no corresponding name is defined in the UID dictionary. - * This method is passed a list of "preferred" transfer syntaxes. - * @param params pointer to association parameters structure - * @param transferSyntax transfer syntax to accept - * @param acceptedRole SCU/SCP role to accept - */ -static OFCondition acceptUnknownContextsWithPreferredTransferSyntaxes( - T_ASC_Parameters * params, - const char* transferSyntaxes[], int transferSyntaxCount, - T_ASC_SC_ROLE acceptedRole) -{ - OFCondition cond = EC_Normal; - /* - ** Accept in the order "least wanted" to "most wanted" transfer - ** syntax. Accepting a transfer syntax will override previously - ** accepted transfer syntaxes. - */ - for (int i = transferSyntaxCount - 1; i >= 0; i--) - { - cond = acceptUnknownContextsWithTransferSyntax(params, transferSyntaxes[i], acceptedRole); - if (cond.bad()) return cond; - } - return cond; -} - - - -namespace Orthanc -{ - namespace Internals - { - OFCondition AssociationCleanup(T_ASC_Association *assoc) - { - OFCondition cond = ASC_dropSCPAssociation(assoc); - if (cond.bad()) - { - LOG(ERROR) << cond.text(); - return cond; - } - - cond = ASC_destroyAssociation(&assoc); - if (cond.bad()) - { - LOG(ERROR) << cond.text(); - return cond; - } - - return cond; - } - - - - CommandDispatcher* AcceptAssociation(const DicomServer& server, T_ASC_Network *net) - { - DcmAssociationConfiguration asccfg; - char buf[BUFSIZ]; - T_ASC_Association *assoc; - OFCondition cond; - OFString sprofile; - OFString temp_str; - - cond = ASC_receiveAssociation(net, &assoc, - /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, - NULL, NULL, - /*opt_secureConnection*/ OFFalse, - DUL_NOBLOCK, 1); - - if (cond == DUL_NOASSOCIATIONREQUEST) - { - // Timeout - AssociationCleanup(assoc); - return NULL; - } - - // if some kind of error occured, take care of it - if (cond.bad()) - { - LOG(ERROR) << "Receiving Association failed: " << cond.text(); - // no matter what kind of error occurred, we need to do a cleanup - AssociationCleanup(assoc); - return NULL; - } - - // Retrieve the AET and the IP address of the remote modality - std::string remoteAet; - std::string remoteIp; - std::string calledAet; - - { - DIC_AE remoteAet_C; - DIC_AE calledAet_C; - DIC_AE remoteIp_C; - DIC_AE calledIP_C; - - if ( -#if DCMTK_VERSION_NUMBER >= 364 - ASC_getAPTitles(assoc->params, remoteAet_C, sizeof(remoteAet_C), calledAet_C, sizeof(calledAet_C), NULL, 0).bad() || - ASC_getPresentationAddresses(assoc->params, remoteIp_C, sizeof(remoteIp_C), calledIP_C, sizeof(calledIP_C)).bad() -#else - ASC_getAPTitles(assoc->params, remoteAet_C, calledAet_C, NULL).bad() || - ASC_getPresentationAddresses(assoc->params, remoteIp_C, calledIP_C).bad() -#endif - ) - { - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_NOREASON - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - remoteIp = std::string(/*OFSTRING_GUARD*/(remoteIp_C)); - remoteAet = std::string(/*OFSTRING_GUARD*/(remoteAet_C)); - calledAet = (/*OFSTRING_GUARD*/(calledAet_C)); - } - - LOG(INFO) << "Association Received from AET " << remoteAet - << " on IP " << remoteIp; - - - { - /* accept the abstract syntaxes for C-ECHO, C-FIND, C-MOVE, - and storage commitment, if presented */ - - std::vector genericTransferSyntaxes; - genericTransferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); - genericTransferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); - genericTransferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); - - std::vector knownAbstractSyntaxes; - - // For C-ECHO (always enabled since Orthanc 1.6.0; in earlier - // versions, only enabled if C-STORE was also enabled) - knownAbstractSyntaxes.push_back(UID_VerificationSOPClass); - - // For C-FIND - if (server.HasFindRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel); - knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel); - } - - if (server.HasWorklistRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel); - } - - // For C-MOVE - if (server.HasMoveRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel); - knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel); - } - - // For C-GET - if (server.HasGetRequestHandlerFactory()) - { - knownAbstractSyntaxes.push_back(UID_GETStudyRootQueryRetrieveInformationModel); - knownAbstractSyntaxes.push_back(UID_GETPatientRootQueryRetrieveInformationModel); - } - - cond = ASC_acceptContextsWithPreferredTransferSyntaxes( - assoc->params, - &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), - &genericTransferSyntaxes[0], genericTransferSyntaxes.size()); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - - - /* storage commitment support, new in Orthanc 1.6.0 */ - if (server.HasStorageCommitmentRequestHandlerFactory()) - { - /** - * "ASC_SC_ROLE_SCUSCP": The "SCU" role is needed to accept - * remote storage commitment requests, and the "SCP" role is - * needed to receive storage commitments answers. - **/ - const char* as[1] = { UID_StorageCommitmentPushModelSOPClass }; - cond = ASC_acceptContextsWithPreferredTransferSyntaxes( - assoc->params, as, 1, - &genericTransferSyntaxes[0], genericTransferSyntaxes.size(), ASC_SC_ROLE_SCUSCP); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - } - } - - - { - /* accept the abstract syntaxes for C-STORE, if presented */ - - std::vector storageTransferSyntaxes; - - // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1 - storageTransferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax); - storageTransferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax); - storageTransferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); - - // New transfer syntaxes supported since Orthanc 0.7.2 - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated)) - { - storageTransferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg)) - { - storageTransferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000)) - { - storageTransferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEG2000TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEG2000TransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless)) - { - storageTransferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); - storageTransferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip)) - { - storageTransferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); - storageTransferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2)) - { - storageTransferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); - storageTransferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); - } - -#if DCMTK_VERSION_NUMBER >= 361 - // New in Orthanc 1.6.0 - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg4)) - { - storageTransferSyntaxes.push_back(UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax); - storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_1TransferSyntax); - storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For2DVideoTransferSyntax); - storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax); - storageTransferSyntaxes.push_back(UID_MPEG4StereoHighProfileLevel4_2TransferSyntax); - } -#endif - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle)) - { - storageTransferSyntaxes.push_back(UID_RLELosslessTransferSyntax); - } - - /* the array of Storage SOP Class UIDs that is defined within "dcmdata/libsrc/dcuid.cc" */ - size_t count = 0; - while (dcmAllStorageSOPClassUIDs[count] != NULL) - { - count++; - } - -#if DCMTK_VERSION_NUMBER >= 362 - // The global variable "numberOfDcmAllStorageSOPClassUIDs" is - // only published if DCMTK >= 3.6.2: - // https://bitbucket.org/sjodogne/orthanc/issues/137 - assert(static_cast(count) == numberOfDcmAllStorageSOPClassUIDs); -#endif - - if (!server.HasGetRequestHandlerFactory()) // dcmqrsrv.cc line 828 - { - // This branch exactly corresponds to Orthanc <= 1.6.1 (in - // which C-GET SCP was not supported) - cond = ASC_acceptContextsWithPreferredTransferSyntaxes( - assoc->params, dcmAllStorageSOPClassUIDs, count, - &storageTransferSyntaxes[0], storageTransferSyntaxes.size()); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - } - else // see dcmqrsrv.cc lines 839 - 876 - { - /* accept storage syntaxes with proposed role */ - int npc = ASC_countPresentationContexts(assoc->params); - for (int i = 0; i < npc; i++) - { - T_ASC_PresentationContext pc; - ASC_getPresentationContext(assoc->params, i, &pc); - if (dcmIsaStorageSOPClassUID(pc.abstractSyntax)) - { - /** - * We are prepared to accept whatever role the caller - * proposes. Normally we can be the SCP of the Storage - * Service Class. When processing the C-GET operation - * we can be the SCU of the Storage Service Class. - **/ - const T_ASC_SC_ROLE role = pc.proposedRole; - - /** - * Accept in the order "least wanted" to "most wanted" - * transfer syntax. Accepting a transfer syntax will - * override previously accepted transfer syntaxes. - **/ - for (int k = static_cast(storageTransferSyntaxes.size()) - 1; k >= 0; k--) - { - for (int j = 0; j < static_cast(pc.transferSyntaxCount); j++) - { - /** - * If the transfer syntax was proposed then we can accept it - * appears in our supported list of transfer syntaxes - **/ - if (strcmp(pc.proposedTransferSyntaxes[j], storageTransferSyntaxes[k]) == 0) - { - cond = ASC_acceptPresentationContext( - assoc->params, pc.presentationContextID, storageTransferSyntaxes[k], role); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - } - } - } - } - } /* for */ - } - - if (!server.HasApplicationEntityFilter() || - server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet)) - { - /* - * Promiscous mode is enabled: Accept everything not known not - * to be a storage SOP class. - **/ - cond = acceptUnknownContextsWithPreferredTransferSyntaxes( - assoc->params, &storageTransferSyntaxes[0], storageTransferSyntaxes.size(), ASC_SC_ROLE_DEFAULT); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - } - } - - /* set our app title */ - ASC_setAPTitles(assoc->params, NULL, NULL, server.GetApplicationEntityTitle().c_str()); - - /* acknowledge or reject this association */ -#if DCMTK_VERSION_NUMBER >= 364 - cond = ASC_getApplicationContextName(assoc->params, buf, sizeof(buf)); -#else - cond = ASC_getApplicationContextName(assoc->params, buf); -#endif - - if ((cond.bad()) || strcmp(buf, UID_StandardApplicationContext) != 0) - { - /* reject: the application context name is not supported */ - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_APPCONTEXTNAMENOTSUPPORTED - }; - - LOG(INFO) << "Association Rejected: Bad Application Context Name: " << buf; - cond = ASC_rejectAssociation(assoc, &rej); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - } - AssociationCleanup(assoc); - return NULL; - } - - /* check the AETs */ - if (!server.IsMyAETitle(calledAet)) - { - LOG(WARNING) << "Rejected association, because of a bad called AET in the request (" << calledAet << ")"; - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - if (server.HasApplicationEntityFilter() && - !server.GetApplicationEntityFilter().IsAllowedConnection(remoteIp, remoteAet, calledAet)) - { - LOG(WARNING) << "Rejected association for remote AET " << remoteAet << " on IP " << remoteIp; - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - if (opt_rejectWithoutImplementationUID && - strlen(assoc->params->theirImplementationClassUID) == 0) - { - /* reject: the no implementation Class UID provided */ - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_NOREASON - }; - - LOG(INFO) << "Association Rejected: No Implementation Class UID provided"; - cond = ASC_rejectAssociation(assoc, &rej); - if (cond.bad()) - { - LOG(INFO) << cond.text(); - } - AssociationCleanup(assoc); - return NULL; - } - - { - cond = ASC_acknowledgeAssociation(assoc); - if (cond.bad()) - { - LOG(ERROR) << cond.text(); - AssociationCleanup(assoc); - return NULL; - } - LOG(INFO) << "Association Acknowledged (Max Send PDV: " << assoc->sendPDVLength << ")"; - if (ASC_countAcceptedPresentationContexts(assoc->params) == 0) - LOG(INFO) << " (but no valid presentation contexts)"; - } - - IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL; - return new CommandDispatcher(server, assoc, remoteIp, remoteAet, calledAet, filter); - } - - - CommandDispatcher::CommandDispatcher(const DicomServer& server, - T_ASC_Association* assoc, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - IApplicationEntityFilter* filter) : - server_(server), - assoc_(assoc), - remoteIp_(remoteIp), - remoteAet_(remoteAet), - calledAet_(calledAet), - filter_(filter) - { - associationTimeout_ = server.GetAssociationTimeout(); - elapsedTimeSinceLastCommand_ = 0; - } - - - CommandDispatcher::~CommandDispatcher() - { - try - { - AssociationCleanup(assoc_); - } - catch (...) - { - LOG(ERROR) << "Some association was not cleanly aborted"; - } - } - - - bool CommandDispatcher::Step() - /* - * This function receives DIMSE commmands over the network connection - * and handles these commands correspondingly. Note that in case of - * storscp only C-ECHO-RQ and C-STORE-RQ commands can be processed. - */ - { - bool finished = false; - - // receive a DIMSE command over the network, with a timeout of 1 second - DcmDataset *statusDetail = NULL; - T_ASC_PresentationContextID presID = 0; - T_DIMSE_Message msg; - - OFCondition cond = DIMSE_receiveCommand(assoc_, DIMSE_NONBLOCKING, 1, &presID, &msg, &statusDetail); - elapsedTimeSinceLastCommand_++; - - // if the command which was received has extra status - // detail information, dump this information - if (statusDetail != NULL) - { - //LOG4CPP_WARN(Internals::GetLogger(), "Status Detail:" << OFendl << DcmObject::PrintHelper(*statusDetail)); - delete statusDetail; - } - - if (cond == DIMSE_OUTOFRESOURCES) - { - finished = true; - } - else if (cond == DIMSE_NODATAAVAILABLE) - { - // Timeout due to DIMSE_NONBLOCKING - if (associationTimeout_ != 0 && - elapsedTimeSinceLastCommand_ >= associationTimeout_) - { - // This timeout is actually a association timeout - finished = true; - } - } - else if (cond == EC_Normal) - { - // Reset the association timeout counter - elapsedTimeSinceLastCommand_ = 0; - - // Convert the type of request to Orthanc's internal type - bool supported = false; - DicomRequestType request; - switch (msg.CommandField) - { - case DIMSE_C_ECHO_RQ: - request = DicomRequestType_Echo; - supported = true; - break; - - case DIMSE_C_STORE_RQ: - request = DicomRequestType_Store; - supported = true; - break; - - case DIMSE_C_MOVE_RQ: - request = DicomRequestType_Move; - supported = true; - break; - - case DIMSE_C_GET_RQ: - request = DicomRequestType_Get; - supported = true; - break; - - case DIMSE_C_FIND_RQ: - request = DicomRequestType_Find; - supported = true; - break; - - case DIMSE_N_ACTION_RQ: - request = DicomRequestType_NAction; - supported = true; - break; - - case DIMSE_N_EVENT_REPORT_RQ: - request = DicomRequestType_NEventReport; - supported = true; - break; - - default: - // we cannot handle this kind of message - cond = DIMSE_BADCOMMANDTYPE; - LOG(ERROR) << "cannot handle command: 0x" << std::hex << msg.CommandField; - break; - } - - - // Check whether this request is allowed by the security filter - if (supported && - filter_ != NULL && - !filter_->IsAllowedRequest(remoteIp_, remoteAet_, calledAet_, request)) - { - LOG(WARNING) << "Rejected " << EnumerationToString(request) - << " request from remote DICOM modality with AET \"" - << remoteAet_ << "\" and hostname \"" << remoteIp_ << "\""; - cond = DIMSE_ILLEGALASSOCIATION; - supported = false; - finished = true; - } - - // in case we received a supported message, process this command - if (supported) - { - // If anything goes wrong, there will be a "BADCOMMANDTYPE" answer - cond = DIMSE_BADCOMMANDTYPE; - - switch (request) - { - case DicomRequestType_Echo: - cond = EchoScp(assoc_, &msg, presID); - break; - - case DicomRequestType_Store: - if (server_.HasStoreRequestHandlerFactory()) // Should always be true - { - std::unique_ptr handler - (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler()); - - if (handler.get() != NULL) - { - cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_, associationTimeout_); - } - } - break; - - case DicomRequestType_Move: - if (server_.HasMoveRequestHandlerFactory()) // Should always be true - { - std::unique_ptr handler - (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler()); - - if (handler.get() != NULL) - { - cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_, associationTimeout_); - } - } - break; - - case DicomRequestType_Get: - if (server_.HasGetRequestHandlerFactory()) // Should always be true - { - std::unique_ptr handler - (server_.GetGetRequestHandlerFactory().ConstructGetRequestHandler()); - - if (handler.get() != NULL) - { - cond = Internals::getScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_, associationTimeout_); - } - } - break; - - case DicomRequestType_Find: - if (server_.HasFindRequestHandlerFactory() || // Should always be true - server_.HasWorklistRequestHandlerFactory()) - { - std::unique_ptr findHandler; - if (server_.HasFindRequestHandlerFactory()) - { - findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler()); - } - - std::unique_ptr worklistHandler; - if (server_.HasWorklistRequestHandlerFactory()) - { - worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler()); - } - - cond = Internals::findScp(assoc_, &msg, presID, server_.GetRemoteModalities(), - findHandler.get(), worklistHandler.get(), - remoteIp_, remoteAet_, calledAet_, associationTimeout_); - } - break; - - case DicomRequestType_NAction: - cond = NActionScp(&msg, presID); - break; - - case DicomRequestType_NEventReport: - cond = NEventReportScp(&msg, presID); - break; - - default: - // Should never happen - break; - } - } - } - else - { - // Bad status, which indicates the closing of the connection by - // the peer or a network error - finished = true; - - LOG(INFO) << cond.text(); - } - - if (finished) - { - if (cond == DUL_PEERREQUESTEDRELEASE) - { - LOG(INFO) << "Association Release"; - ASC_acknowledgeRelease(assoc_); - } - else if (cond == DUL_PEERABORTEDASSOCIATION) - { - LOG(INFO) << "Association Aborted"; - } - else - { - OFString temp_str; - LOG(INFO) << "DIMSE failure (aborting association): " << cond.text(); - /* some kind of error so abort the association */ - ASC_abortAssociation(assoc_); - } - } - - return !finished; - } - - - OFCondition EchoScp(T_ASC_Association * assoc, T_DIMSE_Message * msg, T_ASC_PresentationContextID presID) - { - OFString temp_str; - LOG(INFO) << "Received Echo Request"; - //LOG(DEBUG) << DIMSE_dumpMessage(temp_str, msg->msg.CEchoRQ, DIMSE_INCOMING, NULL, presID)); - - /* the echo succeeded !! */ - OFCondition cond = DIMSE_sendEchoResponse(assoc, presID, &msg->msg.CEchoRQ, STATUS_Success, NULL); - if (cond.bad()) - { - LOG(ERROR) << "Echo SCP Failed: " << cond.text(); - } - return cond; - } - - - static DcmDataset* ReadDataset(T_ASC_Association* assoc, - const char* errorMessage, - int timeout) - { - DcmDataset *tmp = NULL; - T_ASC_PresentationContextID presIdData; - - OFCondition cond = DIMSE_receiveDataSetInMemory( - assoc, (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout, - &presIdData, &tmp, NULL, NULL); - if (!cond.good() || - tmp == NULL) - { - throw OrthancException(ErrorCode_NetworkProtocol, errorMessage); - } - - return tmp; - } - - - static std::string ReadString(DcmDataset& dataset, - const DcmTagKey& tag) - { - const char* s = NULL; - if (!dataset.findAndGetString(tag, s).good() || - s == NULL) - { - char buf[64]; - sprintf(buf, "Missing mandatory tag in dataset: (%04X,%04X)", - tag.getGroup(), tag.getElement()); - throw OrthancException(ErrorCode_NetworkProtocol, buf); - } - - return std::string(s); - } - - - static void ReadSopSequence( - std::vector& sopClassUids, - std::vector& sopInstanceUids, - std::vector* failureReasons, // Can be NULL - DcmDataset& dataset, - const DcmTagKey& tag, - bool mandatory) - { - sopClassUids.clear(); - sopInstanceUids.clear(); - - if (failureReasons) - { - failureReasons->clear(); - } - - DcmSequenceOfItems* sequence = NULL; - if (!dataset.findAndGetSequence(tag, sequence).good() || - sequence == NULL) - { - if (mandatory) - { - char buf[64]; - sprintf(buf, "Missing mandatory sequence in dataset: (%04X,%04X)", - tag.getGroup(), tag.getElement()); - throw OrthancException(ErrorCode_NetworkProtocol, buf); - } - else - { - return; - } - } - - sopClassUids.reserve(sequence->card()); - sopInstanceUids.reserve(sequence->card()); - - if (failureReasons) - { - failureReasons->reserve(sequence->card()); - } - - for (unsigned long i = 0; i < sequence->card(); i++) - { - const char* a = NULL; - const char* b = NULL; - if (!sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPClassUID, a).good() || - !sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPInstanceUID, b).good() || - a == NULL || - b == NULL) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "Missing Referenced SOP Class/Instance UID " - "in storage commitment dataset"); - } - - sopClassUids.push_back(a); - sopInstanceUids.push_back(b); - - if (failureReasons != NULL) - { - Uint16 reason; - if (!sequence->getItem(i)->findAndGetUint16(DCM_FailureReason, reason).good()) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "Missing Failure Reason (0008,1197) " - "in storage commitment dataset"); - } - - failureReasons->push_back(static_cast(reason)); - } - } - } - - - OFCondition CommandDispatcher::NActionScp(T_DIMSE_Message* msg, - T_ASC_PresentationContextID presID) - { - /** - * Starting with Orthanc 1.6.0, only storage commitment is - * supported with DICOM N-ACTION. This corresponds to the case - * where "Action Type ID" equals "1". - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4 - **/ - - if (msg->CommandField != DIMSE_N_ACTION_RQ /* value == 304 == 0x0130 */ || - !server_.HasStorageCommitmentRequestHandlerFactory()) - { - throw OrthancException(ErrorCode_InternalError); - } - - - /** - * Check that the storage commitment request is correctly formatted. - **/ - - const T_DIMSE_N_ActionRQ& request = msg->msg.NActionRQ; - - if (request.ActionTypeID != 1) - { - throw OrthancException(ErrorCode_NotImplemented, - "Only storage commitment is implemented for DICOM N-ACTION SCP"); - } - - if (std::string(request.RequestedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || - std::string(request.RequestedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "Unexpected incoming SOP class or instance UID for storage commitment"); - } - - if (request.DataSetType != DIMSE_DATASET_PRESENT) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "Incoming storage commitment request without a dataset"); - } - - - /** - * Extract the DICOM dataset that is associated with the DIMSE - * message. The content of this dataset is documented in "Table - * J.3-1. Storage Commitment Request - Action Information": - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html#table_J.3-1 - **/ - - std::unique_ptr dataset( - ReadDataset(assoc_, "Cannot read the dataset in N-ACTION SCP", associationTimeout_)); - - std::string transactionUid = ReadString(*dataset, DCM_TransactionUID); - - std::vector sopClassUid, sopInstanceUid; - ReadSopSequence(sopClassUid, sopInstanceUid, NULL, - *dataset, DCM_ReferencedSOPSequence, true /* mandatory */); - - LOG(INFO) << "Incoming storage commitment request, with transaction UID: " << transactionUid; - - for (size_t i = 0; i < sopClassUid.size(); i++) - { - LOG(INFO) << " (" << (i + 1) << "/" << sopClassUid.size() - << ") queried SOP Class/Instance UID: " - << sopClassUid[i] << " / " << sopInstanceUid[i]; - } - - - /** - * Call the Orthanc handler. The list of available DIMSE status - * codes can be found at: - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10 - **/ - - DIC_US dimseStatus; - - try - { - std::unique_ptr handler - (server_.GetStorageCommitmentRequestHandlerFactory(). - ConstructStorageCommitmentRequestHandler()); - - handler->HandleRequest(transactionUid, sopClassUid, sopInstanceUid, - remoteIp_, remoteAet_, calledAet_); - - dimseStatus = 0; // Success - } - catch (OrthancException& e) - { - LOG(ERROR) << "Error while processing an incoming storage commitment request: " << e.What(); - - // Code 0x0110 - "General failure in processing the operation was encountered" - dimseStatus = STATUS_N_ProcessingFailure; - } - - - /** - * Send the DIMSE status back to the SCU. - **/ - - { - T_DIMSE_Message response; - memset(&response, 0, sizeof(response)); - response.CommandField = DIMSE_N_ACTION_RSP; - - T_DIMSE_N_ActionRSP& content = response.msg.NActionRSP; - content.MessageIDBeingRespondedTo = request.MessageID; - strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); - content.DimseStatus = dimseStatus; - strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); - content.ActionTypeID = 0; // Not present, as "O_NACTION_ACTIONTYPEID" not set in "opts" - content.DataSetType = DIMSE_DATASET_NULL; // Dataset is absent in storage commitment response - content.opts = O_NACTION_AFFECTEDSOPCLASSUID | O_NACTION_AFFECTEDSOPINSTANCEUID; - - return DIMSE_sendMessageUsingMemoryData( - assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */, - NULL /* callback */, NULL /* callback context */, NULL /* commandSet */); - } - } - - - OFCondition CommandDispatcher::NEventReportScp(T_DIMSE_Message* msg, - T_ASC_PresentationContextID presID) - { - /** - * Starting with Orthanc 1.6.0, handling N-EVENT-REPORT for - * storage commitment. - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1 - **/ - - if (msg->CommandField != DIMSE_N_EVENT_REPORT_RQ /* value == 256 == 0x0100 */ || - !server_.HasStorageCommitmentRequestHandlerFactory()) - { - throw OrthancException(ErrorCode_InternalError); - } - - - /** - * Check that the storage commitment report is correctly formatted. - **/ - - const T_DIMSE_N_EventReportRQ& report = msg->msg.NEventReportRQ; - - if (report.EventTypeID != 1 /* successful */ && - report.EventTypeID != 2 /* failures exist */) - { - throw OrthancException(ErrorCode_NotImplemented, - "Unknown event for DICOM N-EVENT-REPORT SCP"); - } - - if (std::string(report.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass || - std::string(report.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "Unexpected incoming SOP class or instance UID for storage commitment"); - } - - if (report.DataSetType != DIMSE_DATASET_PRESENT) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "Incoming storage commitment report without a dataset"); - } - - - /** - * Extract the DICOM dataset that is associated with the DIMSE - * message. The content of this dataset is documented in "Table - * J.3-2. Storage Commitment Result - Event Information": - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html#table_J.3-2 - **/ - - std::unique_ptr dataset( - ReadDataset(assoc_, "Cannot read the dataset in N-EVENT-REPORT SCP", associationTimeout_)); - - std::string transactionUid = ReadString(*dataset, DCM_TransactionUID); - - std::vector successSopClassUid, successSopInstanceUid; - ReadSopSequence(successSopClassUid, successSopInstanceUid, NULL, - *dataset, DCM_ReferencedSOPSequence, - (report.EventTypeID == 1) /* mandatory in the case of success */); - - std::vector failedSopClassUid, failedSopInstanceUid; - std::vector failureReasons; - - if (report.EventTypeID == 2 /* failures exist */) - { - ReadSopSequence(failedSopClassUid, failedSopInstanceUid, &failureReasons, - *dataset, DCM_FailedSOPSequence, true); - } - - LOG(INFO) << "Incoming storage commitment report, with transaction UID: " << transactionUid; - - for (size_t i = 0; i < successSopClassUid.size(); i++) - { - LOG(INFO) << " (success " << (i + 1) << "/" << successSopClassUid.size() - << ") SOP Class/Instance UID: " - << successSopClassUid[i] << " / " << successSopInstanceUid[i]; - } - - for (size_t i = 0; i < failedSopClassUid.size(); i++) - { - LOG(INFO) << " (failure " << (i + 1) << "/" << failedSopClassUid.size() - << ") SOP Class/Instance UID: " - << failedSopClassUid[i] << " / " << failedSopInstanceUid[i]; - } - - /** - * Call the Orthanc handler. The list of available DIMSE status - * codes can be found at: - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10 - **/ - - DIC_US dimseStatus; - - try - { - std::unique_ptr handler - (server_.GetStorageCommitmentRequestHandlerFactory(). - ConstructStorageCommitmentRequestHandler()); - - handler->HandleReport(transactionUid, successSopClassUid, successSopInstanceUid, - failedSopClassUid, failedSopInstanceUid, failureReasons, - remoteIp_, remoteAet_, calledAet_); - - dimseStatus = 0; // Success - } - catch (OrthancException& e) - { - LOG(ERROR) << "Error while processing an incoming storage commitment report: " << e.What(); - - // Code 0x0110 - "General failure in processing the operation was encountered" - dimseStatus = STATUS_N_ProcessingFailure; - } - - - /** - * Send the DIMSE status back to the SCU. - **/ - - { - T_DIMSE_Message response; - memset(&response, 0, sizeof(response)); - response.CommandField = DIMSE_N_EVENT_REPORT_RSP; - - T_DIMSE_N_EventReportRSP& content = response.msg.NEventReportRSP; - content.MessageIDBeingRespondedTo = report.MessageID; - strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN); - content.DimseStatus = dimseStatus; - strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN); - content.EventTypeID = 0; // Not present, as "O_NEVENTREPORT_EVENTTYPEID" not set in "opts" - content.DataSetType = DIMSE_DATASET_NULL; // Dataset is absent in storage commitment response - content.opts = O_NEVENTREPORT_AFFECTEDSOPCLASSUID | O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID; - - return DIMSE_sendMessageUsingMemoryData( - assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */, - NULL /* callback */, NULL /* callback context */, NULL /* commandSet */); - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/CommandDispatcher.h --- a/Core/DicomNetworking/Internals/CommandDispatcher.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../DicomServer.h" -#include "../../MultiThreading/IRunnableBySteps.h" - -#include - -namespace Orthanc -{ - namespace Internals - { - OFCondition AssociationCleanup(T_ASC_Association *assoc); - - class CommandDispatcher : public IRunnableBySteps - { - private: - uint32_t associationTimeout_; - uint32_t elapsedTimeSinceLastCommand_; - const DicomServer& server_; - T_ASC_Association* assoc_; - std::string remoteIp_; - std::string remoteAet_; - std::string calledAet_; - IApplicationEntityFilter* filter_; - - OFCondition NActionScp(T_DIMSE_Message* msg, - T_ASC_PresentationContextID presID); - - OFCondition NEventReportScp(T_DIMSE_Message* msg, - T_ASC_PresentationContextID presID); - - public: - CommandDispatcher(const DicomServer& server, - T_ASC_Association* assoc, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - IApplicationEntityFilter* filter); - - virtual ~CommandDispatcher(); - - virtual bool Step(); - }; - - CommandDispatcher* AcceptAssociation(const DicomServer& server, - T_ASC_Network *net); - - OFCondition EchoScp(T_ASC_Association* assoc, - T_DIMSE_Message* msg, - T_ASC_PresentationContextID presID); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/FindScp.cpp --- a/Core/DicomNetworking/Internals/FindScp.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,375 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - - -#include "../../PrecompiledHeaders.h" -#include "FindScp.h" - -#include "../../DicomFormat/DicomArray.h" -#include "../../DicomParsing/FromDcmtkBridge.h" -#include "../../DicomParsing/ToDcmtkBridge.h" -#include "../../Logging.h" -#include "../../OrthancException.h" - -#include -#include - - - -/** - * The function below is extracted from DCMTK 3.6.0, cf. file - * "dcmtk-3.6.0/dcmwlm/libsrc/wldsfs.cc". - **/ - -static void HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(DcmDataset *dataset, - const DcmTagKey &sequenceTagKey) -// Date : May 3, 2005 -// Author : Thomas Wilkens -// Task : This function performs a check on a sequence attribute in the given dataset. At two different places -// in the definition of the DICOM worklist management service, a sequence attribute with a return type -// of 2 is mentioned containing two 1C attributes in its item; the condition of the two 1C attributes -// specifies that in case a sequence item is present, then these two attributes must be existent and -// must contain a value. (I am talking about ReferencedStudySequence and ReferencedPatientSequence.) -// In cases where the sequence attribute contains exactly one item with an empty ReferencedSOPClass -// and an empty ReferencedSOPInstance, we want to remove the item from the sequence. This is what -// this function does. -// Parameters : dataset - [in] Dataset in which the consistency of the sequence attribute shall be checked. -// sequenceTagKey - [in] DcmTagKey of the sequence attribute which shall be checked. -// Return Value : none. -{ - DcmElement *sequenceAttribute = NULL, *referencedSOPClassUIDAttribute = NULL, *referencedSOPInstanceUIDAttribute = NULL; - - // in case the sequence attribute contains exactly one item with an empty - // ReferencedSOPClassUID and an empty ReferencedSOPInstanceUID, remove the item - if( dataset->findAndGetElement( sequenceTagKey, sequenceAttribute ).good() && - ( (DcmSequenceOfItems*)sequenceAttribute )->card() == 1 && - ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPClassUID, referencedSOPClassUIDAttribute ).good() && - referencedSOPClassUIDAttribute->getLength() == 0 && - ( (DcmSequenceOfItems*)sequenceAttribute )->getItem(0)->findAndGetElement( DCM_ReferencedSOPInstanceUID, referencedSOPInstanceUIDAttribute, OFFalse ).good() && - referencedSOPInstanceUIDAttribute->getLength() == 0 ) - { - DcmItem *item = ((DcmSequenceOfItems*)sequenceAttribute)->remove( ((DcmSequenceOfItems*)sequenceAttribute)->getItem(0) ); - delete item; - } -} - - - -namespace Orthanc -{ - namespace - { - struct FindScpData - { - DicomServer::IRemoteModalities* modalities_; - IFindRequestHandler* findHandler_; - IWorklistRequestHandler* worklistHandler_; - DicomFindAnswers answers_; - DcmDataset* lastRequest_; - const std::string* remoteIp_; - const std::string* remoteAet_; - const std::string* calledAet_; - - FindScpData() : answers_(false) - { - } - }; - - - - static void FixWorklistQuery(ParsedDicomFile& query) - { - // TODO: Check out - // WlmDataSourceFileSystem::HandleExistentButEmptyDescriptionAndCodeSequenceAttributes()" - // in DCMTK 3.6.0 - - DcmDataset* dataset = query.GetDcmtkObject().getDataset(); - HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedStudySequence); - HandleExistentButEmptyReferencedStudyOrPatientSequenceAttributes(dataset, DCM_ReferencedPatientSequence); - } - - - static void FixFindQuery(DicomMap& target, - const DicomMap& source) - { - // "The definition of a Data Set in PS3.5 specifically excludes - // the range of groups below group 0008, and this includes in - // particular Meta Information Header elements such as Transfer - // Syntax UID (0002,0010)." - // http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#sect_C.4.1.1.3 - // https://groups.google.com/d/msg/orthanc-users/D3kpPuX8yV0/_zgHOzkMEQAJ - - DicomArray a(source); - - for (size_t i = 0; i < a.GetSize(); i++) - { - if (a.GetElement(i).GetTag().GetGroup() >= 0x0008) - { - target.SetValue(a.GetElement(i).GetTag(), a.GetElement(i).GetValue()); - } - } - } - - - - void FindScpCallback( - /* in */ - void *callbackData, - OFBool cancelled, - T_DIMSE_C_FindRQ *request, - DcmDataset *requestIdentifiers, - int responseCount, - /* out */ - T_DIMSE_C_FindRSP *response, - DcmDataset **responseIdentifiers, - DcmDataset **statusDetail) - { - bzero(response, sizeof(T_DIMSE_C_FindRSP)); - *statusDetail = NULL; - - std::string sopClassUid(request->AffectedSOPClassUID); - - FindScpData& data = *reinterpret_cast(callbackData); - if (data.lastRequest_ == NULL) - { - bool ok = false; - - try - { - RemoteModalityParameters modality; - - /** - * Ensure that the remote modality is known to Orthanc for C-FIND requests. - **/ - - assert(data.modalities_ != NULL); - if (!data.modalities_->LookupAETitle(modality, *data.remoteAet_)) - { - throw OrthancException(ErrorCode_UnknownModality, - "Modality with AET \"" + (*data.remoteAet_) + - "\" is not defined in the \"DicomModalities\" configuration option"); - } - - - if (sopClassUid == UID_FINDModalityWorklistInformationModel) - { - data.answers_.SetWorklist(true); - - if (data.worklistHandler_ != NULL) - { - ParsedDicomFile query(*requestIdentifiers); - FixWorklistQuery(query); - - data.worklistHandler_->Handle(data.answers_, query, - *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, modality.GetManufacturer()); - ok = true; - } - else - { - LOG(ERROR) << "No worklist handler is installed, cannot handle this C-FIND request"; - } - } - else - { - data.answers_.SetWorklist(false); - - if (data.findHandler_ != NULL) - { - std::list sequencesToReturn; - - for (unsigned long i = 0; i < requestIdentifiers->card(); i++) - { - DcmElement* element = requestIdentifiers->getElement(i); - if (element && !element->isLeaf()) - { - const DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); - - DcmSequenceOfItems& sequence = dynamic_cast(*element); - if (sequence.card() != 0) - { - LOG(WARNING) << "Orthanc only supports sequence matching on worklists, " - << "ignoring C-FIND SCU constraint on tag (" << tag.Format() - << ") " << FromDcmtkBridge::GetTagName(*element); - } - - sequencesToReturn.push_back(tag); - } - } - - DicomMap input; - FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers); - - DicomMap filtered; - FixFindQuery(filtered, input); - - data.findHandler_->Handle(data.answers_, filtered, sequencesToReturn, - *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, modality.GetManufacturer()); - ok = true; - } - else - { - LOG(ERROR) << "No C-Find handler is installed, cannot handle this request"; - } - } - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "C-FIND request handler has failed: " << e.What(); - } - - if (!ok) - { - response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; - *responseIdentifiers = NULL; - return; - } - - data.lastRequest_ = requestIdentifiers; - } - else if (data.lastRequest_ != requestIdentifiers) - { - // Internal error! - response->DimseStatus = STATUS_FIND_Failed_UnableToProcess; - *responseIdentifiers = NULL; - return; - } - - if (responseCount <= static_cast(data.answers_.GetSize())) - { - // There are pending results that are still to be sent - response->DimseStatus = STATUS_Pending; - *responseIdentifiers = data.answers_.ExtractDcmDataset(responseCount - 1); - } - else if (data.answers_.IsComplete()) - { - // Success: All the results have been sent - response->DimseStatus = STATUS_Success; - *responseIdentifiers = NULL; - } - else - { - // Success, but the results were too numerous and had to be cropped - LOG(WARNING) << "Too many results for an incoming C-FIND query"; - response->DimseStatus = STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest; - *responseIdentifiers = NULL; - } - } - } - - - OFCondition Internals::findScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - DicomServer::IRemoteModalities& modalities, - IFindRequestHandler* findHandler, - IWorklistRequestHandler* worklistHandler, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - int timeout) - { - FindScpData data; - data.modalities_ = &modalities; - data.findHandler_ = findHandler; - data.worklistHandler_ = worklistHandler; - data.lastRequest_ = NULL; - data.remoteIp_ = &remoteIp; - data.remoteAet_ = &remoteAet; - data.calledAet_ = &calledAet; - - OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, - FindScpCallback, &data, - /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ timeout); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Find SCP Failed: " << cond.text(); - } - - return cond; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/FindScp.h --- a/Core/DicomNetworking/Internals/FindScp.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../DicomServer.h" - -#include - -namespace Orthanc -{ - namespace Internals - { - OFCondition findScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - DicomServer::IRemoteModalities& modalities, - IFindRequestHandler* findHandler, // can be NULL - IWorklistRequestHandler* worklistHandler, // can be NULL - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - int timeout); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/GetScp.cpp --- a/Core/DicomNetworking/Internals/GetScp.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,289 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - - Copyright (C) 1994-2011, OFFIS e.V. - All rights reserved. - - This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - - Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - =========================================================================*/ - - -#include "../../PrecompiledHeaders.h" -#include -#include -#include "GetScp.h" - -#include - -#include "../../DicomParsing/FromDcmtkBridge.h" -#include "../../DicomParsing/ToDcmtkBridge.h" -#include "../../Logging.h" -#include "../../OrthancException.h" - -#include - - -namespace Orthanc -{ - namespace - { - struct GetScpData - { - // Handle returns void. - IGetRequestHandler* handler_; - DcmDataset* lastRequest_; - T_ASC_Association * assoc_; - - std::string remoteIp_; - std::string remoteAet_; - std::string calledAet_; - int timeout_; - - GetScpData() - { - handler_ = NULL; - lastRequest_ = NULL; - assoc_ = NULL; - }; - }; - - static DcmDataset *BuildFailedInstanceList(const std::string& failedUIDs) - { - if (failedUIDs.empty()) - { - return NULL; - } - else - { - std::unique_ptr rspIds(new DcmDataset()); - - if (!DU_putStringDOElement(rspIds.get(), DCM_FailedSOPInstanceUIDList, failedUIDs.c_str())) - { - throw OrthancException(ErrorCode_InternalError, - "getSCP: failed to build DCM_FailedSOPInstanceUIDList"); - } - - return rspIds.release(); - } - } - - static void GetScpCallback( - /* in */ - void *callbackData, - OFBool cancelled, - T_DIMSE_C_GetRQ *request, - DcmDataset *requestIdentifiers, - int responseCount, - /* out */ - T_DIMSE_C_GetRSP *response, - DcmDataset **responseIdentifiers, - DcmDataset **statusDetail) - { - bzero(response, sizeof(T_DIMSE_C_GetRSP)); - *statusDetail = NULL; - *responseIdentifiers = NULL; - - GetScpData& data = *reinterpret_cast(callbackData); - if (data.lastRequest_ == NULL) - { - DicomMap input; - FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers); - - try - { - if (!data.handler_->Handle( - input, data.remoteIp_, data.remoteAet_, data.calledAet_, - data.timeout_ < 0 ? 0 : static_cast(data.timeout_))) - { - response->DimseStatus = STATUS_GET_Failed_UnableToProcess; - return; - } - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "IGetRequestHandler Failed: " << e.What(); - response->DimseStatus = STATUS_GET_Failed_UnableToProcess; - return; - } - - data.lastRequest_ = requestIdentifiers; - } - else if (data.lastRequest_ != requestIdentifiers) - { - // Internal error! - LOG(ERROR) << "IGetRequestHandler Failed: Internal error lastRequestIdentifier"; - response->DimseStatus = STATUS_GET_Failed_UnableToProcess; - return; - } - - if (data.handler_->GetRemainingCount() == 0) - { - response->DimseStatus = STATUS_Success; - } - else - { - IGetRequestHandler::Status status; - - try - { - status = data.handler_->DoNext(data.assoc_); - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "IGetRequestHandler Failed: " << e.What(); - response->DimseStatus = STATUS_GET_Failed_UnableToProcess; - return; - } - - if (status == STATUS_Success) - { - if (responseCount < static_cast(data.handler_->GetRemainingCount())) - { - response->DimseStatus = STATUS_Pending; - } - else - { - response->DimseStatus = STATUS_Success; - } - } - else - { - response->DimseStatus = STATUS_GET_Failed_UnableToProcess; - - if (data.handler_->GetFailedCount() > 0 || - data.handler_->GetWarningCount() > 0) - { - response->DimseStatus = STATUS_GET_Warning_SubOperationsCompleteOneOrMoreFailures; - } - - /* - * if all the sub-operations failed then we need to generate - * a failed or refused status. cf. DICOM part 4, C.4.3.3.1 - * we choose to generate a "Refused - Out of Resources - - * Unable to perform suboperations" status. - */ - if ((data.handler_->GetFailedCount() > 0) && - ((data.handler_->GetCompletedCount() + - data.handler_->GetWarningCount()) == 0)) - { - response->DimseStatus = STATUS_GET_Refused_OutOfResourcesSubOperations; - } - - *responseIdentifiers = BuildFailedInstanceList(data.handler_->GetFailedUids()); - } - } - - response->NumberOfRemainingSubOperations = data.handler_->GetRemainingCount(); - response->NumberOfCompletedSubOperations = data.handler_->GetCompletedCount(); - response->NumberOfFailedSubOperations = data.handler_->GetFailedCount(); - response->NumberOfWarningSubOperations = data.handler_->GetWarningCount(); - } - } - - OFCondition Internals::getScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IGetRequestHandler& handler, - std::string remoteIp, - std::string remoteAet, - std::string calledAet, - int timeout) - { - GetScpData data; - data.lastRequest_ = NULL; - data.handler_ = &handler; - data.assoc_ = assoc; - data.remoteIp_ = remoteIp; - data.remoteAet_ = remoteAet; - data.calledAet_ = calledAet; - data.timeout_ = timeout; - - OFCondition cond = DIMSE_getProvider(assoc, presID, &msg->msg.CGetRQ, - GetScpCallback, &data, - /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ timeout); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Get SCP Failed: " << cond.text(); - } - - return cond; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/GetScp.h --- a/Core/DicomNetworking/Internals/GetScp.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../IGetRequestHandler.h" - -namespace Orthanc -{ - namespace Internals - { - OFCondition getScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IGetRequestHandler& handler, - std::string remoteIp, - std::string remoteAet, - std::string calledAet, - int timeout); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/MoveScp.cpp --- a/Core/DicomNetworking/Internals/MoveScp.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,300 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../../PrecompiledHeaders.h" -#include "MoveScp.h" - -#include - -#include "../../DicomParsing/FromDcmtkBridge.h" -#include "../../DicomParsing/ToDcmtkBridge.h" -#include "../../Logging.h" -#include "../../OrthancException.h" - -#include - - -/** - * Macro specifying whether to apply the patch suggested in issue 66: - * "Orthanc responses C-MOVE with zero Move Originator Message ID" - * https://bitbucket.org/sjodogne/orthanc/issues/66/ - **/ - -#define APPLY_FIX_ISSUE_66 1 - - -namespace Orthanc -{ - namespace - { - struct MoveScpData - { - std::string target_; - IMoveRequestHandler* handler_; - DcmDataset* lastRequest_; - unsigned int subOperationCount_; - unsigned int failureCount_; - unsigned int warningCount_; - std::unique_ptr iterator_; - const std::string* remoteIp_; - const std::string* remoteAet_; - const std::string* calledAet_; - }; - - -#if APPLY_FIX_ISSUE_66 != 1 - static uint16_t GetMessageId(const DicomMap& message) - { - /** - * Retrieve the Message ID (0000,0110) for this C-MOVE request, if - * any. If present, this Message ID will be stored in the Move - * Originator Message ID (0000,1031) field of the C-MOVE response. - * http://dicom.nema.org/dicom/2013/output/chtml/part07/chapter_E.html - **/ - - const DicomValue* value = message.TestAndGetValue(DICOM_TAG_MESSAGE_ID); - - if (value != NULL && - !value->IsNull() && - !value->IsBinary()) - { - try - { - int tmp = boost::lexical_cast(value->GetContent()); - if (tmp >= 0 && tmp <= 0xffff) - { - return static_cast(tmp); - } - } - catch (boost::bad_lexical_cast&) - { - LOG(WARNING) << "Cannot convert the Message ID (\"" << value->GetContent() - << "\") of an incoming C-MOVE request to an integer, assuming zero"; - } - } - - return 0; - } -#endif - - - void MoveScpCallback( - /* in */ - void *callbackData, - OFBool cancelled, - T_DIMSE_C_MoveRQ *request, - DcmDataset *requestIdentifiers, - int responseCount, - /* out */ - T_DIMSE_C_MoveRSP *response, - DcmDataset **responseIdentifiers, - DcmDataset **statusDetail) - { - bzero(response, sizeof(T_DIMSE_C_MoveRSP)); - *statusDetail = NULL; - *responseIdentifiers = NULL; - - MoveScpData& data = *reinterpret_cast(callbackData); - if (data.lastRequest_ == NULL) - { - DicomMap input; - FromDcmtkBridge::ExtractDicomSummary(input, *requestIdentifiers); - - try - { -#if APPLY_FIX_ISSUE_66 == 1 - uint16_t messageId = request->MessageID; -#else - // The line below was the implementation for Orthanc <= 1.3.2 - uint16_t messageId = GetMessageId(input); -#endif - - data.iterator_.reset(data.handler_->Handle(data.target_, input, *data.remoteIp_, *data.remoteAet_, - *data.calledAet_, messageId)); - - if (data.iterator_.get() == NULL) - { - // Internal error! - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - data.subOperationCount_ = data.iterator_->GetSubOperationCount(); - data.failureCount_ = 0; - data.warningCount_ = 0; - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - data.lastRequest_ = requestIdentifiers; - } - else if (data.lastRequest_ != requestIdentifiers) - { - // Internal error! - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - if (data.subOperationCount_ == 0) - { - response->DimseStatus = STATUS_Success; - } - else - { - IMoveRequestIterator::Status status; - - try - { - status = data.iterator_->DoNext(); - } - catch (OrthancException& e) - { - // Internal error! - LOG(ERROR) << "IMoveRequestHandler Failed: " << e.What(); - response->DimseStatus = STATUS_MOVE_Failed_UnableToProcess; - return; - } - - if (status == IMoveRequestIterator::Status_Failure) - { - data.failureCount_++; - } - else if (status == IMoveRequestIterator::Status_Warning) - { - data.warningCount_++; - } - - if (responseCount < static_cast(data.subOperationCount_)) - { - response->DimseStatus = STATUS_Pending; - } - else - { - response->DimseStatus = STATUS_Success; - } - } - - response->NumberOfRemainingSubOperations = data.subOperationCount_ - responseCount; - response->NumberOfCompletedSubOperations = responseCount; - response->NumberOfFailedSubOperations = data.failureCount_; - response->NumberOfWarningSubOperations = data.warningCount_; - } - } - - - OFCondition Internals::moveScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IMoveRequestHandler& handler, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - int timeout) - { - MoveScpData data; - data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination); - data.lastRequest_ = NULL; - data.handler_ = &handler; - data.remoteIp_ = &remoteIp; - data.remoteAet_ = &remoteAet; - data.calledAet_ = &calledAet; - - OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, - MoveScpCallback, &data, - /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ timeout); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Move SCP Failed: " << cond.text(); - } - - return cond; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/MoveScp.h --- a/Core/DicomNetworking/Internals/MoveScp.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../IMoveRequestHandler.h" - -#include - -namespace Orthanc -{ - namespace Internals - { - OFCondition moveScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IMoveRequestHandler& handler, - const std::string& remoteIp, - const std::string& remoteAet, - const std::string& calledAet, - int timeout); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/StoreScp.cpp --- a/Core/DicomNetworking/Internals/StoreScp.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,311 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - -#include "../../PrecompiledHeaders.h" -#include "StoreScp.h" - -#if !defined(DCMTK_VERSION_NUMBER) -# error The macro DCMTK_VERSION_NUMBER must be defined -#endif - -#include "../../DicomParsing/FromDcmtkBridge.h" -#include "../../DicomParsing/ToDcmtkBridge.h" -#include "../../OrthancException.h" -#include "../../Logging.h" - -#include -#include -#include -#include -#include - - -namespace Orthanc -{ - namespace - { - struct StoreCallbackData - { - IStoreRequestHandler* handler; - const std::string* remoteIp; - const char* remoteAET; - const char* calledAET; - const char* modality; - const char* affectedSOPInstanceUID; - uint32_t messageID; - }; - - - static void - storeScpCallback( - void *callbackData, - T_DIMSE_StoreProgress *progress, - T_DIMSE_C_StoreRQ *req, - char * /*imageFileName*/, DcmDataset **imageDataSet, - T_DIMSE_C_StoreRSP *rsp, - DcmDataset **statusDetail) - /* - * This function.is used to indicate progress when storescp receives instance data over the - * network. On the final call to this function (identified by progress->state == DIMSE_StoreEnd) - * this function will store the data set which was received over the network to a file. - * Earlier calls to this function will simply cause some information to be dumped to stdout. - * - * Parameters: - * callbackData - [in] data for this callback function - * progress - [in] The state of progress. (identifies if this is the initial or final call - * to this function, or a call in between these two calls. - * req - [in] The original store request message. - * imageFileName - [in] The path to and name of the file the information shall be written to. - * imageDataSet - [in] The data set which shall be stored in the image file - * rsp - [inout] the C-STORE-RSP message (will be sent after the call to this function) - * statusDetail - [inout] This variable can be used to capture detailed information with regard to - * the status information which is captured in the status element (0000,0900). Note - * that this function does specify any such information, the pointer will be set to NULL. - */ - { - StoreCallbackData *cbdata = OFstatic_cast(StoreCallbackData *, callbackData); - - DIC_UI sopClass; - DIC_UI sopInstance; - - // if this is the final call of this function, save the data which was received to a file - // (note that we could also save the image somewhere else, put it in database, etc.) - if (progress->state == DIMSE_StoreEnd) - { - OFString tmpStr; - - // do not send status detail information - *statusDetail = NULL; - - // Concerning the following line: an appropriate status code is already set in the resp structure, - // it need not be success. For example, if the caller has already detected an out of resources problem - // then the status will reflect this. The callback function is still called to allow cleanup. - //rsp->DimseStatus = STATUS_Success; - - // we want to write the received information to a file only if this information - // is present and the options opt_bitPreserving and opt_ignore are not set. - if ((imageDataSet != NULL) && (*imageDataSet != NULL)) - { - DicomMap summary; - Json::Value dicomJson; - std::string buffer; - - try - { - std::set ignoreTagLength; - - FromDcmtkBridge::ExtractDicomSummary(summary, **imageDataSet); - FromDcmtkBridge::ExtractDicomAsJson(dicomJson, **imageDataSet, ignoreTagLength); - - if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet)) - { - LOG(ERROR) << "cannot write DICOM file to memory"; - rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - } - } - catch (...) - { - rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - } - - // check the image to make sure it is consistent, i.e. that its sopClass and sopInstance correspond - // to those mentioned in the request. If not, set the status in the response message variable. - if (rsp->DimseStatus == STATUS_Success) - { - // which SOP class and SOP instance ? - -#if DCMTK_VERSION_NUMBER >= 364 - if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sizeof(sopClass), - sopInstance, sizeof(sopInstance), /*opt_correctUIDPadding*/ OFFalse)) -#else - if (!DU_findSOPClassAndInstanceInDataSet(*imageDataSet, sopClass, sopInstance, /*opt_correctUIDPadding*/ OFFalse)) -#endif - { - //LOG4CPP_ERROR(Internals::GetLogger(), "bad DICOM file: " << fileName); - rsp->DimseStatus = STATUS_STORE_Error_CannotUnderstand; - } - else if (strcmp(sopClass, req->AffectedSOPClassUID) != 0) - { - rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; - } - else if (strcmp(sopInstance, req->AffectedSOPInstanceUID) != 0) - { - rsp->DimseStatus = STATUS_STORE_Error_DataSetDoesNotMatchSOPClass; - } - else - { - try - { - cbdata->handler->Handle(buffer, summary, dicomJson, *cbdata->remoteIp, cbdata->remoteAET, cbdata->calledAET); - } - catch (OrthancException& e) - { - rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources; - - if (e.GetErrorCode() == ErrorCode_InexistentTag) - { - summary.LogMissingTagsForStore(); - } - else - { - LOG(ERROR) << "Exception while storing DICOM: " << e.What(); - } - } - } - } - } - } - } - } - -/* - * This function processes a DIMSE C-STORE-RQ commmand that was - * received over the network connection. - * - * Parameters: - * assoc - [in] The association (network connection to another DICOM application). - * msg - [in] The DIMSE C-STORE-RQ message that was received. - * presID - [in] The ID of the presentation context which was specified in the PDV which contained - * the DIMSE command. - */ - OFCondition Internals::storeScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IStoreRequestHandler& handler, - const std::string& remoteIp, - int timeout) - { - OFCondition cond = EC_Normal; - T_DIMSE_C_StoreRQ *req; - - // assign the actual information of the C-STORE-RQ command to a local variable - req = &msg->msg.CStoreRQ; - - // intialize some variables - StoreCallbackData data; - data.handler = &handler; - data.remoteIp = &remoteIp; - data.modality = dcmSOPClassUIDToModality(req->AffectedSOPClassUID/*, "UNKNOWN"*/); - if (data.modality == NULL) - data.modality = "UNKNOWN"; - - data.affectedSOPInstanceUID = req->AffectedSOPInstanceUID; - data.messageID = req->MessageID; - if (assoc && assoc->params) - { - data.remoteAET = assoc->params->DULparams.callingAPTitle; - data.calledAET = assoc->params->DULparams.calledAPTitle; - } - else - { - data.remoteAET = ""; - data.calledAET = ""; - } - - DcmFileFormat dcmff; - - // store SourceApplicationEntityTitle in metaheader - if (assoc && assoc->params) - { - const char *aet = assoc->params->DULparams.callingAPTitle; - if (aet) dcmff.getMetaInfo()->putAndInsertString(DCM_SourceApplicationEntityTitle, aet); - } - - // define an address where the information which will be received over the network will be stored - DcmDataset *dset = dcmff.getDataset(); - - cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset, - storeScpCallback, &data, - /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), - /*opt_dimse_timeout*/ timeout); - - // if some error occured, dump corresponding information and remove the outfile if necessary - if (cond.bad()) - { - OFString temp_str; - LOG(ERROR) << "Store SCP Failed: " << cond.text(); - } - - // return return value - return cond; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/Internals/StoreScp.h --- a/Core/DicomNetworking/Internals/StoreScp.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../IStoreRequestHandler.h" - -#include - -namespace Orthanc -{ - namespace Internals - { - OFCondition storeScp(T_ASC_Association * assoc, - T_DIMSE_Message * msg, - T_ASC_PresentationContextID presID, - IStoreRequestHandler& handler, - const std::string& remoteIp, - int timeout); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/NetworkingCompatibility.h --- a/Core/DicomNetworking/NetworkingCompatibility.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - - -#ifdef _WIN32 -/** - * "The maximum length, in bytes, of the string returned in the buffer - * pointed to by the name parameter is dependent on the namespace provider, - * but this string must be 256 bytes or less. - * http://msdn.microsoft.com/en-us/library/windows/desktop/ms738527(v=vs.85).aspx - **/ -# define HOST_NAME_MAX 256 -# include -#endif - - -#if !defined(HOST_NAME_MAX) && defined(_POSIX_HOST_NAME_MAX) -/** - * TO IMPROVE: "_POSIX_HOST_NAME_MAX is only the minimum value that - * HOST_NAME_MAX can ever have [...] Therefore you cannot allocate an - * array of size _POSIX_HOST_NAME_MAX, invoke gethostname() and expect - * that the result will fit." - * http://lists.gnu.org/archive/html/bug-gnulib/2009-08/msg00128.html - **/ -# define HOST_NAME_MAX _POSIX_HOST_NAME_MAX -#endif diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/RemoteModalityParameters.cpp --- a/Core/DicomNetworking/RemoteModalityParameters.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,378 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "RemoteModalityParameters.h" - -#include "../Logging.h" -#include "../OrthancException.h" -#include "../SerializationToolbox.h" - -#include -#include - - -static const char* KEY_AET = "AET"; -static const char* KEY_ALLOW_ECHO = "AllowEcho"; -static const char* KEY_ALLOW_FIND = "AllowFind"; -static const char* KEY_ALLOW_GET = "AllowGet"; -static const char* KEY_ALLOW_MOVE = "AllowMove"; -static const char* KEY_ALLOW_STORE = "AllowStore"; -static const char* KEY_ALLOW_N_ACTION = "AllowNAction"; -static const char* KEY_ALLOW_N_EVENT_REPORT = "AllowEventReport"; -static const char* KEY_ALLOW_STORAGE_COMMITMENT = "AllowStorageCommitment"; -static const char* KEY_ALLOW_TRANSCODING = "AllowTranscoding"; -static const char* KEY_HOST = "Host"; -static const char* KEY_MANUFACTURER = "Manufacturer"; -static const char* KEY_PORT = "Port"; - - -namespace Orthanc -{ - void RemoteModalityParameters::Clear() - { - aet_ = "ORTHANC"; - host_ = "127.0.0.1"; - port_ = 104; - manufacturer_ = ModalityManufacturer_Generic; - allowEcho_ = true; - allowStore_ = true; - allowFind_ = true; - allowMove_ = true; - allowGet_ = true; - allowNAction_ = true; // For storage commitment - allowNEventReport_ = true; // For storage commitment - allowTranscoding_ = true; - } - - - RemoteModalityParameters::RemoteModalityParameters(const std::string& aet, - const std::string& host, - uint16_t port, - ModalityManufacturer manufacturer) - { - Clear(); - SetApplicationEntityTitle(aet); - SetHost(host); - SetPortNumber(port); - SetManufacturer(manufacturer); - } - - - static void CheckPortNumber(int value) - { - if (value <= 0 || - value >= 65535) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "A TCP port number must be in range [1..65534], but found: " + - boost::lexical_cast(value)); - } - } - - - static uint16_t ReadPortNumber(const Json::Value& value) - { - int tmp; - - switch (value.type()) - { - case Json::intValue: - case Json::uintValue: - tmp = value.asInt(); - break; - - case Json::stringValue: - try - { - tmp = boost::lexical_cast(value.asString()); - } - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - break; - - default: - throw OrthancException(ErrorCode_BadFileFormat); - } - - CheckPortNumber(tmp); - return static_cast(tmp); - } - - - void RemoteModalityParameters::SetPortNumber(uint16_t port) - { - CheckPortNumber(port); - port_ = port; - } - - - void RemoteModalityParameters::UnserializeArray(const Json::Value& serialized) - { - assert(serialized.type() == Json::arrayValue); - - if ((serialized.size() != 3 && - serialized.size() != 4) || - serialized[0].type() != Json::stringValue || - serialized[1].type() != Json::stringValue || - (serialized.size() == 4 && - serialized[3].type() != Json::stringValue)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - aet_ = serialized[0].asString(); - host_ = serialized[1].asString(); - port_ = ReadPortNumber(serialized[2]); - - if (serialized.size() == 4) - { - manufacturer_ = StringToModalityManufacturer(serialized[3].asString()); - } - else - { - manufacturer_ = ModalityManufacturer_Generic; - } - } - - - void RemoteModalityParameters::UnserializeObject(const Json::Value& serialized) - { - assert(serialized.type() == Json::objectValue); - - aet_ = SerializationToolbox::ReadString(serialized, KEY_AET); - host_ = SerializationToolbox::ReadString(serialized, KEY_HOST); - - if (serialized.isMember(KEY_PORT)) - { - port_ = ReadPortNumber(serialized[KEY_PORT]); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - if (serialized.isMember(KEY_MANUFACTURER)) - { - manufacturer_ = StringToModalityManufacturer - (SerializationToolbox::ReadString(serialized, KEY_MANUFACTURER)); - } - else - { - manufacturer_ = ModalityManufacturer_Generic; - } - - if (serialized.isMember(KEY_ALLOW_ECHO)) - { - allowEcho_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_ECHO); - } - - if (serialized.isMember(KEY_ALLOW_FIND)) - { - allowFind_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_FIND); - } - - if (serialized.isMember(KEY_ALLOW_STORE)) - { - allowStore_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_STORE); - } - - if (serialized.isMember(KEY_ALLOW_GET)) - { - allowGet_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_GET); - } - - if (serialized.isMember(KEY_ALLOW_MOVE)) - { - allowMove_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_MOVE); - } - - if (serialized.isMember(KEY_ALLOW_N_ACTION)) - { - allowNAction_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_N_ACTION); - } - - if (serialized.isMember(KEY_ALLOW_N_EVENT_REPORT)) - { - allowNEventReport_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_N_EVENT_REPORT); - } - - if (serialized.isMember(KEY_ALLOW_STORAGE_COMMITMENT)) - { - bool allow = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_STORAGE_COMMITMENT); - allowNAction_ = allow; - allowNEventReport_ = allow; - } - - if (serialized.isMember(KEY_ALLOW_TRANSCODING)) - { - allowTranscoding_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_TRANSCODING); - } - } - - - bool RemoteModalityParameters::IsRequestAllowed(DicomRequestType type) const - { - switch (type) - { - case DicomRequestType_Echo: - return allowEcho_; - - case DicomRequestType_Find: - return allowFind_; - - case DicomRequestType_Get: - return allowGet_; - - case DicomRequestType_Move: - return allowMove_; - - case DicomRequestType_Store: - return allowStore_; - - case DicomRequestType_NAction: - return allowNAction_; - - case DicomRequestType_NEventReport: - return allowNEventReport_; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - void RemoteModalityParameters::SetRequestAllowed(DicomRequestType type, - bool allowed) - { - switch (type) - { - case DicomRequestType_Echo: - allowEcho_ = allowed; - break; - - case DicomRequestType_Find: - allowFind_ = allowed; - break; - - case DicomRequestType_Get: - allowGet_ = allowed; - break; - - case DicomRequestType_Move: - allowMove_ = allowed; - break; - - case DicomRequestType_Store: - allowStore_ = allowed; - break; - - case DicomRequestType_NAction: - allowNAction_ = allowed; - break; - - case DicomRequestType_NEventReport: - allowNEventReport_ = allowed; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - bool RemoteModalityParameters::IsAdvancedFormatNeeded() const - { - return (!allowEcho_ || - !allowStore_ || - !allowFind_ || - !allowGet_ || - !allowMove_ || - !allowNAction_ || - !allowNEventReport_ || - !allowTranscoding_); - } - - - void RemoteModalityParameters::Serialize(Json::Value& target, - bool forceAdvancedFormat) const - { - if (forceAdvancedFormat || - IsAdvancedFormatNeeded()) - { - target = Json::objectValue; - target[KEY_AET] = aet_; - target[KEY_HOST] = host_; - target[KEY_PORT] = port_; - target[KEY_MANUFACTURER] = EnumerationToString(manufacturer_); - target[KEY_ALLOW_ECHO] = allowEcho_; - target[KEY_ALLOW_STORE] = allowStore_; - target[KEY_ALLOW_FIND] = allowFind_; - target[KEY_ALLOW_GET] = allowGet_; - target[KEY_ALLOW_MOVE] = allowMove_; - target[KEY_ALLOW_N_ACTION] = allowNAction_; - target[KEY_ALLOW_N_EVENT_REPORT] = allowNEventReport_; - target[KEY_ALLOW_TRANSCODING] = allowTranscoding_; - } - else - { - target = Json::arrayValue; - target.append(GetApplicationEntityTitle()); - target.append(GetHost()); - target.append(GetPortNumber()); - target.append(EnumerationToString(GetManufacturer())); - } - } - - - void RemoteModalityParameters::Unserialize(const Json::Value& serialized) - { - Clear(); - - switch (serialized.type()) - { - case Json::objectValue: - UnserializeObject(serialized); - break; - - case Json::arrayValue: - UnserializeArray(serialized); - break; - - default: - throw OrthancException(ErrorCode_BadFileFormat); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/RemoteModalityParameters.h --- a/Core/DicomNetworking/RemoteModalityParameters.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,146 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include -#include -#include - -namespace Orthanc -{ - class RemoteModalityParameters - { - private: - std::string aet_; - std::string host_; - uint16_t port_; - ModalityManufacturer manufacturer_; - bool allowEcho_; - bool allowStore_; - bool allowFind_; - bool allowMove_; - bool allowGet_; - bool allowNAction_; - bool allowNEventReport_; - bool allowTranscoding_; - - void Clear(); - - void UnserializeArray(const Json::Value& serialized); - - void UnserializeObject(const Json::Value& serialized); - - public: - RemoteModalityParameters() - { - Clear(); - } - - RemoteModalityParameters(const Json::Value& serialized) - { - Unserialize(serialized); - } - - RemoteModalityParameters(const std::string& aet, - const std::string& host, - uint16_t port, - ModalityManufacturer manufacturer); - - const std::string& GetApplicationEntityTitle() const - { - return aet_; - } - - void SetApplicationEntityTitle(const std::string& aet) - { - aet_ = aet; - } - - const std::string& GetHost() const - { - return host_; - } - - void SetHost(const std::string& host) - { - host_ = host; - } - - uint16_t GetPortNumber() const - { - return port_; - } - - void SetPortNumber(uint16_t port); - - ModalityManufacturer GetManufacturer() const - { - return manufacturer_; - } - - void SetManufacturer(ModalityManufacturer manufacturer) - { - manufacturer_ = manufacturer; - } - - void SetManufacturer(const std::string& manufacturer) - { - manufacturer_ = StringToModalityManufacturer(manufacturer); - } - - bool IsRequestAllowed(DicomRequestType type) const; - - void SetRequestAllowed(DicomRequestType type, - bool allowed); - - void Unserialize(const Json::Value& modality); - - bool IsAdvancedFormatNeeded() const; - - void Serialize(Json::Value& target, - bool forceAdvancedFormat) const; - - bool IsTranscodingAllowed() const - { - return allowTranscoding_; - } - - void SetTranscodingAllowed(bool allowed) - { - allowTranscoding_ = allowed; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/TimeoutDicomConnectionManager.cpp --- a/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "TimeoutDicomConnectionManager.h" - -#include "../Logging.h" -#include "../OrthancException.h" - -namespace Orthanc -{ - static boost::posix_time::ptime GetNow() - { - return boost::posix_time::microsec_clock::universal_time(); - } - - - TimeoutDicomConnectionManager::Lock::Lock(TimeoutDicomConnectionManager& that, - const std::string& localAet, - const RemoteModalityParameters& remote) : - that_(that), - lock_(that_.mutex_) - { - // Calling "Touch()" will be done by the "~Lock()" destructor - that_.OpenInternal(localAet, remote); - } - - - TimeoutDicomConnectionManager::Lock::~Lock() - { - that_.TouchInternal(); - } - - - DicomStoreUserConnection& TimeoutDicomConnectionManager::Lock::GetConnection() - { - if (that_.connection_.get() == NULL) - { - // The allocation should have been done by "that_.Open()" in the constructor - throw OrthancException(ErrorCode_InternalError); - } - else - { - return *that_.connection_; - } - } - - - // Mutex must be locked - void TimeoutDicomConnectionManager::TouchInternal() - { - lastUse_ = GetNow(); - } - - - // Mutex must be locked - void TimeoutDicomConnectionManager::OpenInternal(const std::string& localAet, - const RemoteModalityParameters& remote) - { - DicomAssociationParameters other(localAet, remote); - - if (connection_.get() == NULL || - !connection_->GetParameters().IsEqual(other)) - { - connection_.reset(new DicomStoreUserConnection(other)); - } - } - - - // Mutex must be locked - void TimeoutDicomConnectionManager::CloseInternal() - { - if (connection_.get() != NULL) - { - LOG(INFO) << "Closing inactive DICOM association with modality: " - << connection_->GetParameters().GetRemoteModality().GetApplicationEntityTitle(); - - connection_.reset(NULL); - } - } - - - void TimeoutDicomConnectionManager::SetInactivityTimeout(unsigned int milliseconds) - { - boost::mutex::scoped_lock lock(mutex_); - timeout_ = boost::posix_time::milliseconds(milliseconds); - CloseInternal(); - } - - - unsigned int TimeoutDicomConnectionManager::GetInactivityTimeout() - { - boost::mutex::scoped_lock lock(mutex_); - return static_cast(timeout_.total_milliseconds()); - } - - - void TimeoutDicomConnectionManager::CloseIfInactive() - { - boost::mutex::scoped_lock lock(mutex_); - - if (connection_.get() != NULL && - (GetNow() - lastUse_) >= timeout_) - { - CloseInternal(); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomNetworking/TimeoutDicomConnectionManager.h --- a/Core/DicomNetworking/TimeoutDicomConnectionManager.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_DCMTK_NETWORKING) -# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be defined -#endif - -#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 -# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be 1 to use this file -#endif - - -#include "../Compatibility.h" -#include "DicomStoreUserConnection.h" - -#include -#include - -namespace Orthanc -{ - /** - * This class corresponds to a singleton to a DICOM SCU connection. - **/ - class TimeoutDicomConnectionManager : public boost::noncopyable - { - private: - boost::mutex mutex_; - std::unique_ptr connection_; - boost::posix_time::ptime lastUse_; - boost::posix_time::time_duration timeout_; - - // Mutex must be locked - void TouchInternal(); - - // Mutex must be locked - void OpenInternal(const std::string& localAet, - const RemoteModalityParameters& remote); - - // Mutex must be locked - void CloseInternal(); - - public: - class Lock : public boost::noncopyable - { - private: - TimeoutDicomConnectionManager& that_; - boost::mutex::scoped_lock lock_; - - public: - Lock(TimeoutDicomConnectionManager& that, - const std::string& localAet, - const RemoteModalityParameters& remote); - - ~Lock(); - - DicomStoreUserConnection& GetConnection(); - }; - - TimeoutDicomConnectionManager() : - timeout_(boost::posix_time::milliseconds(1000)) - { - } - - void SetInactivityTimeout(unsigned int milliseconds); - - unsigned int GetInactivityTimeout(); // In milliseconds - - void Close(); - - void CloseIfInactive(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/DcmtkTranscoder.cpp --- a/Core/DicomParsing/DcmtkTranscoder.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,352 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DcmtkTranscoder.h" - - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) -# error Macro ORTHANC_ENABLE_DCMTK_JPEG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) -# error Macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined -#endif - - -#include "FromDcmtkBridge.h" -#include "../OrthancException.h" - -#include -#include // for DJ_RPLossy -#include // for DJ_RPLossless -#include // for DJLSRepresentationParameter - - -namespace Orthanc -{ - static bool GetBitsStored(uint16_t& bitsStored, - DcmDataset& dataset) - { - return dataset.findAndGetUint16(DCM_BitsStored, bitsStored).good(); - } - - - void DcmtkTranscoder::SetLossyQuality(unsigned int quality) - { - if (quality <= 0 || - quality > 100) - { - throw OrthancException( - ErrorCode_ParameterOutOfRange, - "The quality for lossy transcoding must be an integer between 1 and 100, received: " + - boost::lexical_cast(quality)); - } - else - { - LOG(INFO) << "Quality for lossy transcoding using DCMTK is set to: " << quality; - lossyQuality_ = quality; - } - } - - - bool DcmtkTranscoder::InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */, - DcmFileFormat& dicom, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) - { - if (dicom.getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - DicomTransferSyntax syntax; - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(syntax, dicom)) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot determine the transfer syntax"); - } - - uint16_t bitsStored; - bool hasBitsStored = GetBitsStored(bitsStored, *dicom.getDataset()); - - std::string sourceSopInstanceUid = IDicomTranscoder::GetSopInstanceUid(dicom); - - if (allowedSyntaxes.find(syntax) != allowedSyntaxes.end()) - { - // No transcoding is needed - return true; - } - - if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianImplicit) != allowedSyntaxes.end() && - FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianImplicit, NULL)) - { - selectedSyntax = DicomTransferSyntax_LittleEndianImplicit; - return true; - } - - if (allowedSyntaxes.find(DicomTransferSyntax_LittleEndianExplicit) != allowedSyntaxes.end() && - FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_LittleEndianExplicit, NULL)) - { - selectedSyntax = DicomTransferSyntax_LittleEndianExplicit; - return true; - } - - if (allowedSyntaxes.find(DicomTransferSyntax_BigEndianExplicit) != allowedSyntaxes.end() && - FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_BigEndianExplicit, NULL)) - { - selectedSyntax = DicomTransferSyntax_BigEndianExplicit; - return true; - } - - if (allowedSyntaxes.find(DicomTransferSyntax_DeflatedLittleEndianExplicit) != allowedSyntaxes.end() && - FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_DeflatedLittleEndianExplicit, NULL)) - { - selectedSyntax = DicomTransferSyntax_DeflatedLittleEndianExplicit; - return true; - } - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess1) != allowedSyntaxes.end() && - allowNewSopInstanceUid && - (!hasBitsStored || bitsStored == 8)) - { - // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossy parameters(lossyQuality_); - - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess1, ¶meters)) - { - selectedSyntax = DicomTransferSyntax_JPEGProcess1; - return true; - } - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess2_4) != allowedSyntaxes.end() && - allowNewSopInstanceUid && - (!hasBitsStored || bitsStored <= 12)) - { - // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossy parameters(lossyQuality_); - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess2_4, ¶meters)) - { - selectedSyntax = DicomTransferSyntax_JPEGProcess2_4; - return true; - } - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14) != allowedSyntaxes.end()) - { - // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossless parameters(6 /* opt_selection_value */, - 0 /* opt_point_transform */); - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14, ¶meters)) - { - selectedSyntax = DicomTransferSyntax_JPEGProcess14; - return true; - } - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - if (allowedSyntaxes.find(DicomTransferSyntax_JPEGProcess14SV1) != allowedSyntaxes.end()) - { - // Check out "dcmjpeg/apps/dcmcjpeg.cc" - DJ_RPLossless parameters(6 /* opt_selection_value */, - 0 /* opt_point_transform */); - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGProcess14SV1, ¶meters)) - { - selectedSyntax = DicomTransferSyntax_JPEGProcess14SV1; - return true; - } - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - if (allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossless) != allowedSyntaxes.end()) - { - // Check out "dcmjpls/apps/dcmcjpls.cc" - DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */, - OFTrue /* opt_useLosslessProcess */); - - /** - * WARNING: This call results in a segmentation fault if using - * the DCMTK package 3.6.2 from Ubuntu 18.04. - **/ - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossless, ¶meters)) - { - selectedSyntax = DicomTransferSyntax_JPEGLSLossless; - return true; - } - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - if (allowNewSopInstanceUid && - allowedSyntaxes.find(DicomTransferSyntax_JPEGLSLossy) != allowedSyntaxes.end()) - { - // Check out "dcmjpls/apps/dcmcjpls.cc" - DJLSRepresentationParameter parameters(2 /* opt_nearlossless_deviation */, - OFFalse /* opt_useLosslessProcess */); - - /** - * WARNING: This call results in a segmentation fault if using - * the DCMTK package 3.6.2 from Ubuntu 18.04. - **/ - if (FromDcmtkBridge::Transcode(dicom, DicomTransferSyntax_JPEGLSLossy, ¶meters)) - { - selectedSyntax = DicomTransferSyntax_JPEGLSLossy; - return true; - } - } -#endif - - return false; - } - - - bool DcmtkTranscoder::IsSupported(DicomTransferSyntax syntax) - { - if (syntax == DicomTransferSyntax_LittleEndianImplicit || - syntax == DicomTransferSyntax_LittleEndianExplicit || - syntax == DicomTransferSyntax_BigEndianExplicit || - syntax == DicomTransferSyntax_DeflatedLittleEndianExplicit) - { - return true; - } - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - if (syntax == DicomTransferSyntax_JPEGProcess1 || - syntax == DicomTransferSyntax_JPEGProcess2_4 || - syntax == DicomTransferSyntax_JPEGProcess14 || - syntax == DicomTransferSyntax_JPEGProcess14SV1) - { - return true; - } -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - if (syntax == DicomTransferSyntax_JPEGLSLossless || - syntax == DicomTransferSyntax_JPEGLSLossy) - { - return true; - } -#endif - - return false; - } - - - bool DcmtkTranscoder::Transcode(DicomImage& target, - DicomImage& source /* in, "GetParsed()" possibly modified */, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) - { - target.Clear(); - - DicomTransferSyntax sourceSyntax; - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, source.GetParsed())) - { - LOG(ERROR) << "Unsupport transfer syntax for transcoding"; - return false; - } - - { - std::string s; - for (std::set::const_iterator - it = allowedSyntaxes.begin(); it != allowedSyntaxes.end(); ++it) - { - if (!s.empty()) - { - s += ", "; - } - - s += GetTransferSyntaxUid(*it); - } - - if (s.empty()) - { - s = ""; - } - - LOG(INFO) << "DCMTK transcoding from " << GetTransferSyntaxUid(sourceSyntax) - << " to one of: " << s; - } - -#if !defined(NDEBUG) - const std::string sourceSopInstanceUid = GetSopInstanceUid(source.GetParsed()); -#endif - - DicomTransferSyntax targetSyntax; - if (allowedSyntaxes.find(sourceSyntax) != allowedSyntaxes.end()) - { - // No transcoding is needed - target.AcquireParsed(source); - target.AcquireBuffer(source); - return true; - } - else if (InplaceTranscode(targetSyntax, source.GetParsed(), - allowedSyntaxes, allowNewSopInstanceUid)) - { - // Sanity check - DicomTransferSyntax targetSyntax2; - if (FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax2, source.GetParsed()) && - targetSyntax == targetSyntax2 && - allowedSyntaxes.find(targetSyntax2) != allowedSyntaxes.end()) - { - target.AcquireParsed(source); - source.Clear(); - -#if !defined(NDEBUG) - // Only run the sanity check in debug mode - CheckTranscoding(target, sourceSyntax, sourceSopInstanceUid, - allowedSyntaxes, allowNewSopInstanceUid); -#endif - - return true; - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - } - else - { - // Cannot transcode - return false; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/DcmtkTranscoder.h --- a/Core/DicomParsing/DcmtkTranscoder.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING) -# error Macro ORTHANC_ENABLE_DCMTK_TRANSCODING must be defined to use this file -#endif - -#if ORTHANC_ENABLE_DCMTK_TRANSCODING != 1 -# error Transcoding is disabled, cannot compile this file -#endif - -#include "IDicomTranscoder.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC DcmtkTranscoder : public IDicomTranscoder - { - private: - unsigned int lossyQuality_; - - bool InplaceTranscode(DicomTransferSyntax& selectedSyntax /* out */, - DcmFileFormat& dicom, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid); - - public: - DcmtkTranscoder() : - lossyQuality_(90) - { - } - - void SetLossyQuality(unsigned int quality); - - unsigned int GetLossyQuality() const - { - return lossyQuality_; - } - - static bool IsSupported(DicomTransferSyntax syntax); - - virtual bool Transcode(DicomImage& target, - DicomImage& source /* in, "GetParsed()" possibly modified */, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/DicomDirWriter.cpp --- a/Core/DicomParsing/DicomDirWriter.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,602 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: DCMTK 3.6.0 - Module: http://dicom.offis.de/dcmtk.php.en - -Copyright (C) 1994-2011, OFFIS e.V. -All rights reserved. - -This software and supporting documentation were developed by - - OFFIS e.V. - R&D Division Health - Escherweg 2 - 26121 Oldenburg, Germany - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -- Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -- Neither the name of OFFIS nor the names of its contributors may be - used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -=========================================================================*/ - - - -/*** - - Validation: - - # sudo apt-get install dicom3tools - # dciodvfy DICOMDIR 2>&1 | less - # dcentvfy DICOMDIR 2>&1 | less - - http://www.dclunie.com/dicom3tools/dciodvfy.html - - DICOMDIR viewer working with Wine under Linux: - http://www.microdicom.com/ - - ***/ - - -#include "../PrecompiledHeaders.h" -#include "DicomDirWriter.h" - -#include "FromDcmtkBridge.h" -#include "ToDcmtkBridge.h" - -#include "../Compatibility.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "../TemporaryFile.h" -#include "../Toolbox.h" -#include "../SystemToolbox.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include "dcmtk/dcmdata/dcvrda.h" /* for class DcmDate */ -#include "dcmtk/dcmdata/dcvrtm.h" /* for class DcmTime */ - -#include - -namespace Orthanc -{ - class DicomDirWriter::PImpl - { - private: - bool utc_; - std::string fileSetId_; - bool extendedSopClass_; - TemporaryFile file_; - std::unique_ptr dir_; - - typedef std::pair IndexKey; - typedef std::map Index; - Index index_; - - - DcmDicomDir& GetDicomDir() - { - if (dir_.get() == NULL) - { - dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), - fileSetId_.c_str())); - //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8)); - } - - return *dir_; - } - - - DcmDirectoryRecord& GetRoot() - { - return GetDicomDir().getRootRecord(); - } - - - static bool GetUtf8TagValue(std::string& result, - DcmItem& source, - Encoding encoding, - bool hasCodeExtensions, - const DcmTagKey& key) - { - DcmElement* element = NULL; - result.clear(); - - if (source.findAndGetElement(key, element).good()) - { - char* s = NULL; - if (element->isLeaf() && - element->getString(s).good()) - { - if (s != NULL) - { - result = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); - } - - return true; - } - } - - return false; - } - - - static void SetTagValue(DcmDirectoryRecord& target, - const DcmTagKey& key, - const std::string& valueUtf8) - { - std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii); - - if (!target.putAndInsertString(key, s.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - - static bool CopyString(DcmDirectoryRecord& target, - DcmDataset& source, - Encoding encoding, - bool hasCodeExtensions, - const DcmTagKey& key, - bool optional, - bool copyEmpty) - { - if (optional && - !source.tagExistsWithValue(key) && - !(copyEmpty && source.tagExists(key))) - { - return false; - } - - std::string value; - bool found = GetUtf8TagValue(value, source, encoding, hasCodeExtensions, key); - - if (!found) - { - // We don't raise an exception if "!optional", even if this - // results in an invalid DICOM file - value.clear(); - } - - SetTagValue(target, key, value); - return found; - } - - - static void CopyStringType1(DcmDirectoryRecord& target, - DcmDataset& source, - Encoding encoding, - bool hasCodeExtensions, - const DcmTagKey& key) - { - CopyString(target, source, encoding, hasCodeExtensions, key, false, false); - } - - static void CopyStringType1C(DcmDirectoryRecord& target, - DcmDataset& source, - Encoding encoding, - bool hasCodeExtensions, - const DcmTagKey& key) - { - CopyString(target, source, encoding, hasCodeExtensions, key, true, false); - } - - static void CopyStringType2(DcmDirectoryRecord& target, - DcmDataset& source, - Encoding encoding, - bool hasCodeExtensions, - const DcmTagKey& key) - { - CopyString(target, source, encoding, hasCodeExtensions, key, false, true); - } - - static void CopyStringType3(DcmDirectoryRecord& target, - DcmDataset& source, - Encoding encoding, - bool hasCodeExtensions, - const DcmTagKey& key) - { - CopyString(target, source, encoding, hasCodeExtensions, key, true, true); - } - - - public: - PImpl() : - utc_(true), // By default, use UTC (universal time, not local time) - fileSetId_("ORTHANC_MEDIA"), - extendedSopClass_(false) - { - } - - bool IsUtcUsed() const - { - return utc_; - } - - - void SetUtcUsed(bool utc) - { - utc_ = utc; - } - - void EnableExtendedSopClass(bool enable) - { - if (enable) - { - LOG(WARNING) << "Generating a DICOMDIR with type 3 attributes, " - << "which leads to an Extended SOP Class"; - } - - extendedSopClass_ = enable; - } - - bool IsExtendedSopClass() const - { - return extendedSopClass_; - } - - void FillPatient(DcmDirectoryRecord& record, - DcmDataset& dicom, - Encoding encoding, - bool hasCodeExtensions) - { - // cf. "DicomDirInterface::buildPatientRecord()" - - CopyStringType1C(record, dicom, encoding, hasCodeExtensions, DCM_PatientID); - CopyStringType2(record, dicom, encoding, hasCodeExtensions, DCM_PatientName); - } - - void FillStudy(DcmDirectoryRecord& record, - DcmDataset& dicom, - Encoding encoding, - bool hasCodeExtensions) - { - // cf. "DicomDirInterface::buildStudyRecord()" - - std::string nowDate, nowTime; - SystemToolbox::GetNowDicom(nowDate, nowTime, utc_); - - std::string studyDate; - if (!GetUtf8TagValue(studyDate, dicom, encoding, hasCodeExtensions, DCM_StudyDate) && - !GetUtf8TagValue(studyDate, dicom, encoding, hasCodeExtensions, DCM_SeriesDate) && - !GetUtf8TagValue(studyDate, dicom, encoding, hasCodeExtensions, DCM_AcquisitionDate) && - !GetUtf8TagValue(studyDate, dicom, encoding, hasCodeExtensions, DCM_ContentDate)) - { - studyDate = nowDate; - } - - std::string studyTime; - if (!GetUtf8TagValue(studyTime, dicom, encoding, hasCodeExtensions, DCM_StudyTime) && - !GetUtf8TagValue(studyTime, dicom, encoding, hasCodeExtensions, DCM_SeriesTime) && - !GetUtf8TagValue(studyTime, dicom, encoding, hasCodeExtensions, DCM_AcquisitionTime) && - !GetUtf8TagValue(studyTime, dicom, encoding, hasCodeExtensions, DCM_ContentTime)) - { - studyTime = nowTime; - } - - /* copy attribute values from dataset to study record */ - SetTagValue(record, DCM_StudyDate, studyDate); - SetTagValue(record, DCM_StudyTime, studyTime); - CopyStringType2(record, dicom, encoding, hasCodeExtensions, DCM_StudyDescription); - CopyStringType1(record, dicom, encoding, hasCodeExtensions, DCM_StudyInstanceUID); - /* use type 1C instead of 1 in order to avoid unwanted overwriting */ - CopyStringType1C(record, dicom, encoding, hasCodeExtensions, DCM_StudyID); - CopyStringType2(record, dicom, encoding, hasCodeExtensions, DCM_AccessionNumber); - } - - void FillSeries(DcmDirectoryRecord& record, - DcmDataset& dicom, - Encoding encoding, - bool hasCodeExtensions) - { - // cf. "DicomDirInterface::buildSeriesRecord()" - - /* copy attribute values from dataset to series record */ - CopyStringType1(record, dicom, encoding, hasCodeExtensions, DCM_Modality); - CopyStringType1(record, dicom, encoding, hasCodeExtensions, DCM_SeriesInstanceUID); - /* use type 1C instead of 1 in order to avoid unwanted overwriting */ - CopyStringType1C(record, dicom, encoding, hasCodeExtensions, DCM_SeriesNumber); - - // Add extended (non-standard) type 3 tags, those are not generated by DCMTK - // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part02/sect_7.3.html - // https://groups.google.com/d/msg/orthanc-users/Y7LOvZMDeoc/9cp3kDgxAwAJ - if (extendedSopClass_) - { - CopyStringType3(record, dicom, encoding, hasCodeExtensions, DCM_SeriesDescription); - } - } - - void FillInstance(DcmDirectoryRecord& record, - DcmDataset& dicom, - Encoding encoding, - bool hasCodeExtensions, - DcmMetaInfo& metaInfo, - const char* path) - { - // cf. "DicomDirInterface::buildImageRecord()" - - /* copy attribute values from dataset to image record */ - CopyStringType1(record, dicom, encoding, hasCodeExtensions, DCM_InstanceNumber); - //CopyElementType1C(record, dicom, encoding, hasCodeExtensions, DCM_ImageType); - - // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record); - - std::string sopClassUid, sopInstanceUid, transferSyntaxUid; - if (!GetUtf8TagValue(sopClassUid, dicom, encoding, hasCodeExtensions, DCM_SOPClassUID) || - !GetUtf8TagValue(sopInstanceUid, dicom, encoding, hasCodeExtensions, DCM_SOPInstanceUID) || - !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, hasCodeExtensions, DCM_TransferSyntaxUID)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - SetTagValue(record, DCM_ReferencedFileID, path); - SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid); - SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid); - SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid); - } - - - - bool CreateResource(DcmDirectoryRecord*& target, - ResourceType level, - ParsedDicomFile& dicom, - const char* filename, - const char* path) - { - DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); - - bool hasCodeExtensions; - Encoding encoding = dicom.DetectEncoding(hasCodeExtensions); - - bool found; - std::string id; - E_DirRecType type; - - switch (level) - { - case ResourceType_Patient: - if (!GetUtf8TagValue(id, dataset, encoding, hasCodeExtensions, DCM_PatientID)) - { - // Be tolerant about missing patient ID. Fixes issue #124 - // (GET /studies/ID/media fails for certain dicom file). - id = ""; - } - - found = true; - type = ERT_Patient; - break; - - case ResourceType_Study: - found = GetUtf8TagValue(id, dataset, encoding, hasCodeExtensions, DCM_StudyInstanceUID); - type = ERT_Study; - break; - - case ResourceType_Series: - found = GetUtf8TagValue(id, dataset, encoding, hasCodeExtensions, DCM_SeriesInstanceUID); - type = ERT_Series; - break; - - case ResourceType_Instance: - found = GetUtf8TagValue(id, dataset, encoding, hasCodeExtensions, DCM_SOPInstanceUID); - type = ERT_Image; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - if (!found) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - IndexKey key = std::make_pair(level, std::string(id.c_str())); - Index::iterator it = index_.find(key); - - if (it != index_.end()) - { - target = it->second; - return false; // Already existing - } - - std::unique_ptr record(new DcmDirectoryRecord(type, NULL, filename)); - - switch (level) - { - case ResourceType_Patient: - FillPatient(*record, dataset, encoding, hasCodeExtensions); - break; - - case ResourceType_Study: - FillStudy(*record, dataset, encoding, hasCodeExtensions); - break; - - case ResourceType_Series: - FillSeries(*record, dataset, encoding, hasCodeExtensions); - break; - - case ResourceType_Instance: - FillInstance(*record, dataset, encoding, hasCodeExtensions, *dicom.GetDcmtkObject().getMetaInfo(), path); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - CopyStringType1C(*record, dataset, encoding, hasCodeExtensions, DCM_SpecificCharacterSet); - - target = record.get(); - GetRoot().insertSub(record.release()); - index_[key] = target; - - return true; // Newly created - } - - void Read(std::string& s) - { - if (!GetDicomDir().write(DICOMDIR_DEFAULT_TRANSFERSYNTAX, - EET_UndefinedLength /*encodingType*/, - EGL_withoutGL /*groupLength*/).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - - file_.Read(s); - } - - void SetFileSetId(const std::string& id) - { - dir_.reset(NULL); - fileSetId_ = id; - } - }; - - - DicomDirWriter::DicomDirWriter() : pimpl_(new PImpl) - { - } - - void DicomDirWriter::SetUtcUsed(bool utc) - { - pimpl_->SetUtcUsed(utc); - } - - bool DicomDirWriter::IsUtcUsed() const - { - return pimpl_->IsUtcUsed(); - } - - void DicomDirWriter::SetFileSetId(const std::string& id) - { - pimpl_->SetFileSetId(id); - } - - void DicomDirWriter::Add(const std::string& directory, - const std::string& filename, - ParsedDicomFile& dicom) - { - std::string path; - if (directory.empty()) - { - path = filename; - } - else - { - if (directory[directory.length() - 1] == '/' || - directory[directory.length() - 1] == '\\') - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - path = directory + '\\' + filename; - } - - DcmDirectoryRecord* instance; - bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str()); - if (isNewInstance) - { - DcmDirectoryRecord* series; - bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL); - series->insertSub(instance); - - if (isNewSeries) - { - DcmDirectoryRecord* study; - bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL); - study->insertSub(series); - - if (isNewStudy) - { - DcmDirectoryRecord* patient; - pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL); - patient->insertSub(study); - } - } - } - } - - void DicomDirWriter::Encode(std::string& target) - { - pimpl_->Read(target); - } - - - void DicomDirWriter::EnableExtendedSopClass(bool enable) - { - pimpl_->EnableExtendedSopClass(enable); - } - - - bool DicomDirWriter::IsExtendedSopClass() const - { - return pimpl_->IsExtendedSopClass(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/DicomDirWriter.h --- a/Core/DicomParsing/DicomDirWriter.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ParsedDicomFile.h" - -#include - -namespace Orthanc -{ - class DicomDirWriter : public boost::noncopyable - { - private: - class PImpl; - boost::shared_ptr pimpl_; - - public: - DicomDirWriter(); - - void SetUtcUsed(bool utc); - - bool IsUtcUsed() const; - - void SetFileSetId(const std::string& id); - - void Add(const std::string& directory, - const std::string& filename, - ParsedDicomFile& dicom); - - void Encode(std::string& target); - - void EnableExtendedSopClass(bool enable); - - bool IsExtendedSopClass() const; - }; - -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/DicomModification.cpp --- a/Core/DicomParsing/DicomModification.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1522 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomModification.h" - -#include "../Compatibility.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "../SerializationToolbox.h" -#include "FromDcmtkBridge.h" -#include "ITagVisitor.h" - -#include // For std::unique_ptr - - -static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 = - "Orthanc " ORTHANC_VERSION " - PS 3.15-2008 Table E.1-1"; - -static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2017c = - "Orthanc " ORTHANC_VERSION " - PS 3.15-2017c Table E.1-1 Basic Profile"; - -namespace Orthanc -{ - class DicomModification::RelationshipsVisitor : public ITagVisitor - { - private: - DicomModification& that_; - - bool IsEnabled(const DicomTag& tag) const - { - return (!that_.IsCleared(tag) && - !that_.IsRemoved(tag) && - !that_.IsReplaced(tag)); - } - - void RemoveIfEnabled(ParsedDicomFile& dicom, - const DicomTag& tag) const - { - if (IsEnabled(tag)) - { - dicom.Remove(tag); - } - } - - - public: - RelationshipsVisitor(DicomModification& that) : - that_(that) - { - } - - virtual void VisitNotSupported(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr) - { - } - - virtual void VisitEmptySequence(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag) - { - } - - virtual void VisitBinary(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const void* data, - size_t size) - { - } - - virtual void VisitIntegers(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector& values) - { - } - - virtual void VisitDoubles(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector& value) - { - } - - virtual void VisitAttributes(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - const std::vector& value) - { - } - - virtual Action VisitString(std::string& newValue, - const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::string& value) - { - if (!IsEnabled(tag)) - { - return Action_None; - } - else if (parentTags.size() == 2 && - parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE && - parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE && - tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID) - { - // in RT-STRUCT, this ReferencedSOPInstanceUID is actually referencing a StudyInstanceUID !! - // (observed in many data sets including: https://wiki.cancerimagingarchive.net/display/Public/Lung+CT+Segmentation+Challenge+2017) - // tested in test_anonymize_relationships_5 - newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study); - return Action_Replace; - } - else if (tag == DICOM_TAG_FRAME_OF_REFERENCE_UID || - tag == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID || - tag == DICOM_TAG_REFERENCED_SOP_INSTANCE_UID || - tag == DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID) - { - newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Instance); - return Action_Replace; - } - else if (parentTags.size() == 1 && - parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE && - tag == DICOM_TAG_STUDY_INSTANCE_UID) - { - newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Study); - return Action_Replace; - } - else if (parentTags.size() == 2 && - parentTags[0] == DICOM_TAG_CURRENT_REQUESTED_PROCEDURE_EVIDENCE_SEQUENCE && - parentTags[1] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE && - tag == DICOM_TAG_SERIES_INSTANCE_UID) - { - newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series); - return Action_Replace; - } - else if (parentTags.size() == 3 && - parentTags[0] == DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE && - parentTags[1] == DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE && - parentTags[2] == DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE && - tag == DICOM_TAG_SERIES_INSTANCE_UID) - { - newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series); - return Action_Replace; - } - else if (parentTags.size() == 1 && - parentTags[0] == DICOM_TAG_REFERENCED_SERIES_SEQUENCE && - tag == DICOM_TAG_SERIES_INSTANCE_UID) - { - newValue = that_.MapDicomIdentifier(Toolbox::StripSpaces(value), ResourceType_Series); - return Action_Replace; - } - else - { - return Action_None; - } - } - - void RemoveRelationships(ParsedDicomFile& dicom) const - { - // Sequences containing the UID relationships - RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_IMAGE_SEQUENCE); - RemoveIfEnabled(dicom, DICOM_TAG_SOURCE_IMAGE_SEQUENCE); - - // Individual tags - RemoveIfEnabled(dicom, DICOM_TAG_FRAME_OF_REFERENCE_UID); - - // The tags below should never occur at the first level of the - // hierarchy, but remove them anyway - RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_UID); - RemoveIfEnabled(dicom, DICOM_TAG_REFERENCED_SOP_INSTANCE_UID); - RemoveIfEnabled(dicom, DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID); - } - }; - - - bool DicomModification::CancelReplacement(const DicomTag& tag) - { - Replacements::iterator it = replacements_.find(tag); - - if (it != replacements_.end()) - { - delete it->second; - replacements_.erase(it); - return true; - } - else - { - return false; - } - } - - - void DicomModification::ReplaceInternal(const DicomTag& tag, - const Json::Value& value) - { - Replacements::iterator it = replacements_.find(tag); - - if (it != replacements_.end()) - { - delete it->second; - it->second = NULL; // In the case of an exception during the clone - it->second = new Json::Value(value); // Clone - } - else - { - replacements_[tag] = new Json::Value(value); // Clone - } - } - - - void DicomModification::ClearReplacements() - { - for (Replacements::iterator it = replacements_.begin(); - it != replacements_.end(); ++it) - { - delete it->second; - } - - replacements_.clear(); - } - - - void DicomModification::MarkNotOrthancAnonymization() - { - Replacements::iterator it = replacements_.find(DICOM_TAG_DEIDENTIFICATION_METHOD); - - if (it != replacements_.end() && - (it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2008 || - it->second->asString() == ORTHANC_DEIDENTIFICATION_METHOD_2017c)) - { - delete it->second; - replacements_.erase(it); - } - } - - void DicomModification::RegisterMappedDicomIdentifier(const std::string& original, - const std::string& mapped, - ResourceType level) - { - UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); - - if (previous == uidMap_.end()) - { - uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); - } - } - - std::string DicomModification::MapDicomIdentifier(const std::string& original, - ResourceType level) - { - std::string mapped; - - UidMap::const_iterator previous = uidMap_.find(std::make_pair(level, original)); - - if (previous == uidMap_.end()) - { - if (identifierGenerator_ == NULL) - { - mapped = FromDcmtkBridge::GenerateUniqueIdentifier(level); - } - else - { - if (!identifierGenerator_->Apply(mapped, original, level, currentSource_)) - { - throw OrthancException(ErrorCode_InternalError, - "Unable to generate an anonymized ID"); - } - } - - uidMap_.insert(std::make_pair(std::make_pair(level, original), mapped)); - } - else - { - mapped = previous->second; - } - - return mapped; - } - - - void DicomModification::MapDicomTags(ParsedDicomFile& dicom, - ResourceType level) - { - std::unique_ptr tag; - - switch (level) - { - case ResourceType_Study: - tag.reset(new DicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); - break; - - case ResourceType_Series: - tag.reset(new DicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - break; - - case ResourceType_Instance: - tag.reset(new DicomTag(DICOM_TAG_SOP_INSTANCE_UID)); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - std::string original; - if (!dicom.GetTagValue(original, *tag)) - { - original = ""; - } - - std::string mapped = MapDicomIdentifier(Toolbox::StripSpaces(original), level); - - dicom.Replace(*tag, mapped, - false /* don't try and decode data URI scheme for UIDs */, - DicomReplaceMode_InsertIfAbsent, privateCreator_); - } - - - DicomModification::DicomModification() : - removePrivateTags_(false), - level_(ResourceType_Instance), - allowManualIdentifiers_(true), - keepStudyInstanceUid_(false), - keepSeriesInstanceUid_(false), - keepSopInstanceUid_(false), - updateReferencedRelationships_(true), - isAnonymization_(false), - //privateCreator_("PrivateCreator"), - identifierGenerator_(NULL) - { - } - - DicomModification::~DicomModification() - { - ClearReplacements(); - } - - void DicomModification::Keep(const DicomTag& tag) - { - bool wasRemoved = IsRemoved(tag); - bool wasCleared = IsCleared(tag); - - removals_.erase(tag); - clearings_.erase(tag); - - bool wasReplaced = CancelReplacement(tag); - - if (tag == DICOM_TAG_STUDY_INSTANCE_UID) - { - keepStudyInstanceUid_ = true; - } - else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) - { - keepSeriesInstanceUid_ = true; - } - else if (tag == DICOM_TAG_SOP_INSTANCE_UID) - { - keepSopInstanceUid_ = true; - } - else if (tag.IsPrivate()) - { - privateTagsToKeep_.insert(tag); - } - else if (!wasRemoved && - !wasReplaced && - !wasCleared) - { - LOG(WARNING) << "Marking this tag as to be kept has no effect: " << tag.Format(); - } - - MarkNotOrthancAnonymization(); - } - - void DicomModification::Remove(const DicomTag& tag) - { - removals_.insert(tag); - clearings_.erase(tag); - CancelReplacement(tag); - privateTagsToKeep_.erase(tag); - - MarkNotOrthancAnonymization(); - } - - void DicomModification::Clear(const DicomTag& tag) - { - removals_.erase(tag); - clearings_.insert(tag); - CancelReplacement(tag); - privateTagsToKeep_.erase(tag); - - MarkNotOrthancAnonymization(); - } - - bool DicomModification::IsRemoved(const DicomTag& tag) const - { - return removals_.find(tag) != removals_.end(); - } - - bool DicomModification::IsCleared(const DicomTag& tag) const - { - return clearings_.find(tag) != clearings_.end(); - } - - void DicomModification::Replace(const DicomTag& tag, - const Json::Value& value, - bool safeForAnonymization) - { - clearings_.erase(tag); - removals_.erase(tag); - privateTagsToKeep_.erase(tag); - ReplaceInternal(tag, value); - - if (!safeForAnonymization) - { - MarkNotOrthancAnonymization(); - } - } - - - bool DicomModification::IsReplaced(const DicomTag& tag) const - { - return replacements_.find(tag) != replacements_.end(); - } - - const Json::Value& DicomModification::GetReplacement(const DicomTag& tag) const - { - Replacements::const_iterator it = replacements_.find(tag); - - if (it == replacements_.end()) - { - throw OrthancException(ErrorCode_InexistentItem); - } - else - { - return *it->second; - } - } - - - std::string DicomModification::GetReplacementAsString(const DicomTag& tag) const - { - const Json::Value& json = GetReplacement(tag); - - if (json.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - else - { - return json.asString(); - } - } - - - void DicomModification::SetRemovePrivateTags(bool removed) - { - removePrivateTags_ = removed; - - if (!removed) - { - MarkNotOrthancAnonymization(); - } - } - - void DicomModification::SetLevel(ResourceType level) - { - uidMap_.clear(); - level_ = level; - - if (level != ResourceType_Patient) - { - MarkNotOrthancAnonymization(); - } - } - - - void DicomModification::SetupAnonymization2008() - { - // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles - // https://raw.githubusercontent.com/jodogne/dicom-specification/master/2008/08_15pu.pdf - - removals_.insert(DicomTag(0x0008, 0x0014)); // Instance Creator UID - //removals_.insert(DicomTag(0x0008, 0x0018)); // SOP Instance UID => set in Apply() - removals_.insert(DicomTag(0x0008, 0x0050)); // Accession Number - removals_.insert(DicomTag(0x0008, 0x0080)); // Institution Name - removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address - removals_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name - removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address - removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers - removals_.insert(DicomTag(0x0008, 0x1010)); // Station Name - removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description - removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description - removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name - removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record - removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name - removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study - removals_.insert(DicomTag(0x0008, 0x1070)); // Operators' Name - removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description - //removals_.insert(DicomTag(0x0008, 0x1155)); // Referenced SOP Instance UID => RelationshipsVisitor - removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description - //removals_.insert(DicomTag(0x0010, 0x0010)); // Patient's Name => cf. below (*) - //removals_.insert(DicomTag(0x0010, 0x0020)); // Patient ID => cf. below (*) - removals_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date - removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time - removals_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex - removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient Ids - removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names - removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age - removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size - removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight - removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator - removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group - removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation - removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History - removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments - removals_.insert(DicomTag(0x0018, 0x1000)); // Device Serial Number - removals_.insert(DicomTag(0x0018, 0x1030)); // Protocol Name - //removals_.insert(DicomTag(0x0020, 0x000d)); // Study Instance UID => set in Apply() - //removals_.insert(DicomTag(0x0020, 0x000e)); // Series Instance UID => set in Apply() - removals_.insert(DicomTag(0x0020, 0x0010)); // Study ID - //removals_.insert(DicomTag(0x0020, 0x0052)); // Frame of Reference UID => cf. RelationshipsVisitor - removals_.insert(DicomTag(0x0020, 0x0200)); // Synchronization Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments - removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence - removals_.insert(DicomTag(0x0040, 0xa124)); // UID - removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence - removals_.insert(DicomTag(0x0088, 0x0140)); // Storage Media File-set UID - //removals_.insert(DicomTag(0x3006, 0x0024)); // Referenced Frame of Reference UID => RelationshipsVisitor - //removals_.insert(DicomTag(0x3006, 0x00c2)); // Related Frame of Reference UID => RelationshipsVisitor - - // Some more removals (from the experience of DICOM files at the CHU of Liege) - removals_.insert(DicomTag(0x0010, 0x1040)); // Patient's Address - removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician - removals_.insert(DicomTag(0x0010, 0x2154)); // PatientTelephoneNumbers - removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts - - // Set the DeidentificationMethod tag - ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2008); - } - - - void DicomModification::SetupAnonymization2017c() - { - /** - * This is Table E.1-1 from PS 3.15-2017c (DICOM Part 15: Security - * and System Management Profiles), "basic profile" column. It was - * generated automatically with the - * "../Resources/GenerateAnonymizationProfile.py" script. - * https://raw.githubusercontent.com/jodogne/dicom-specification/master/2017c/part15.pdf - **/ - - // TODO: (50xx,xxxx) with rule X // Curve Data - // TODO: (60xx,3000) with rule X // Overlay Data - // TODO: (60xx,4000) with rule X // Overlay Comments - // Tag (0x0008, 0x0018) is set in Apply() /* U */ // SOP Instance UID - // Tag (0x0008, 0x1140) => RelationshipsVisitor /* X/Z/U* */ // Referenced Image Sequence - // Tag (0x0008, 0x1155) => RelationshipsVisitor /* U */ // Referenced SOP Instance UID - // Tag (0x0008, 0x2112) => RelationshipsVisitor /* X/Z/U* */ // Source Image Sequence - // Tag (0x0010, 0x0010) is set below (*) /* Z */ // Patient's Name - // Tag (0x0010, 0x0020) is set below (*) /* Z */ // Patient ID - // Tag (0x0020, 0x000d) is set in Apply() /* U */ // Study Instance UID - // Tag (0x0020, 0x000e) is set in Apply() /* U */ // Series Instance UID - // Tag (0x0020, 0x0052) => RelationshipsVisitor /* U */ // Frame of Reference UID - // Tag (0x3006, 0x0024) => RelationshipsVisitor /* U */ // Referenced Frame of Reference UID - // Tag (0x3006, 0x00c2) => RelationshipsVisitor /* U */ // Related Frame of Reference UID - clearings_.insert(DicomTag(0x0008, 0x0020)); // Study Date - clearings_.insert(DicomTag(0x0008, 0x0023)); /* Z/D */ // Content Date - clearings_.insert(DicomTag(0x0008, 0x0030)); // Study Time - clearings_.insert(DicomTag(0x0008, 0x0033)); /* Z/D */ // Content Time - clearings_.insert(DicomTag(0x0008, 0x0050)); // Accession Number - clearings_.insert(DicomTag(0x0008, 0x0090)); // Referring Physician's Name - clearings_.insert(DicomTag(0x0008, 0x009c)); // Consulting Physician's Name - clearings_.insert(DicomTag(0x0010, 0x0030)); // Patient's Birth Date - clearings_.insert(DicomTag(0x0010, 0x0040)); // Patient's Sex - clearings_.insert(DicomTag(0x0018, 0x0010)); /* Z/D */ // Contrast Bolus Agent - clearings_.insert(DicomTag(0x0020, 0x0010)); // Study ID - clearings_.insert(DicomTag(0x0040, 0x1101)); /* D */ // Person Identification Code Sequence - clearings_.insert(DicomTag(0x0040, 0x2016)); // Placer Order Number / Imaging Service Request - clearings_.insert(DicomTag(0x0040, 0x2017)); // Filler Order Number / Imaging Service Request - clearings_.insert(DicomTag(0x0040, 0xa073)); /* D */ // Verifying Observer Sequence - clearings_.insert(DicomTag(0x0040, 0xa075)); /* D */ // Verifying Observer Name - clearings_.insert(DicomTag(0x0040, 0xa088)); // Verifying Observer Identification Code Sequence - clearings_.insert(DicomTag(0x0040, 0xa123)); /* D */ // Person Name - clearings_.insert(DicomTag(0x0070, 0x0001)); /* D */ // Graphic Annotation Sequence - clearings_.insert(DicomTag(0x0070, 0x0084)); // Content Creator's Name - removals_.insert(DicomTag(0x0000, 0x1000)); // Affected SOP Instance UID - removals_.insert(DicomTag(0x0000, 0x1001)); /* TODO UID */ // Requested SOP Instance UID - removals_.insert(DicomTag(0x0002, 0x0003)); /* TODO UID */ // Media Storage SOP Instance UID - removals_.insert(DicomTag(0x0004, 0x1511)); /* TODO UID */ // Referenced SOP Instance UID in File - removals_.insert(DicomTag(0x0008, 0x0014)); /* TODO UID */ // Instance Creator UID - removals_.insert(DicomTag(0x0008, 0x0015)); // Instance Coercion DateTime - removals_.insert(DicomTag(0x0008, 0x0021)); /* X/D */ // Series Date - removals_.insert(DicomTag(0x0008, 0x0022)); /* X/Z */ // Acquisition Date - removals_.insert(DicomTag(0x0008, 0x0024)); // Overlay Date - removals_.insert(DicomTag(0x0008, 0x0025)); // Curve Date - removals_.insert(DicomTag(0x0008, 0x002a)); /* X/D */ // Acquisition DateTime - removals_.insert(DicomTag(0x0008, 0x0031)); /* X/D */ // Series Time - removals_.insert(DicomTag(0x0008, 0x0032)); /* X/Z */ // Acquisition Time - removals_.insert(DicomTag(0x0008, 0x0034)); // Overlay Time - removals_.insert(DicomTag(0x0008, 0x0035)); // Curve Time - removals_.insert(DicomTag(0x0008, 0x0058)); /* TODO UID */ // Failed SOP Instance UID List - removals_.insert(DicomTag(0x0008, 0x0080)); /* X/Z/D */ // Institution Name - removals_.insert(DicomTag(0x0008, 0x0081)); // Institution Address - removals_.insert(DicomTag(0x0008, 0x0082)); /* X/Z/D */ // Institution Code Sequence - removals_.insert(DicomTag(0x0008, 0x0092)); // Referring Physician's Address - removals_.insert(DicomTag(0x0008, 0x0094)); // Referring Physician's Telephone Numbers - removals_.insert(DicomTag(0x0008, 0x0096)); // Referring Physician Identification Sequence - removals_.insert(DicomTag(0x0008, 0x009d)); // Consulting Physician Identification Sequence - removals_.insert(DicomTag(0x0008, 0x0201)); // Timezone Offset From UTC - removals_.insert(DicomTag(0x0008, 0x1010)); /* X/Z/D */ // Station Name - removals_.insert(DicomTag(0x0008, 0x1030)); // Study Description - removals_.insert(DicomTag(0x0008, 0x103e)); // Series Description - removals_.insert(DicomTag(0x0008, 0x1040)); // Institutional Department Name - removals_.insert(DicomTag(0x0008, 0x1048)); // Physician(s) of Record - removals_.insert(DicomTag(0x0008, 0x1049)); // Physician(s) of Record Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1050)); // Performing Physicians' Name - removals_.insert(DicomTag(0x0008, 0x1052)); // Performing Physician Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1060)); // Name of Physician(s) Reading Study - removals_.insert(DicomTag(0x0008, 0x1062)); // Physician(s) Reading Study Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1070)); /* X/Z/D */ // Operators' Name - removals_.insert(DicomTag(0x0008, 0x1072)); /* X/D */ // Operators' Identification Sequence - removals_.insert(DicomTag(0x0008, 0x1080)); // Admitting Diagnoses Description - removals_.insert(DicomTag(0x0008, 0x1084)); // Admitting Diagnoses Code Sequence - removals_.insert(DicomTag(0x0008, 0x1110)); /* X/Z */ // Referenced Study Sequence - removals_.insert(DicomTag(0x0008, 0x1111)); /* X/Z/D */ // Referenced Performed Procedure Step Sequence - removals_.insert(DicomTag(0x0008, 0x1120)); // Referenced Patient Sequence - removals_.insert(DicomTag(0x0008, 0x1195)); /* TODO UID */ // Transaction UID - removals_.insert(DicomTag(0x0008, 0x2111)); // Derivation Description - removals_.insert(DicomTag(0x0008, 0x3010)); /* TODO UID */ // Irradiation Event UID - removals_.insert(DicomTag(0x0008, 0x4000)); // Identifying Comments - removals_.insert(DicomTag(0x0010, 0x0021)); // Issuer of Patient ID - removals_.insert(DicomTag(0x0010, 0x0032)); // Patient's Birth Time - removals_.insert(DicomTag(0x0010, 0x0050)); // Patient's Insurance Plan Code Sequence - removals_.insert(DicomTag(0x0010, 0x0101)); // Patient's Primary Language Code Sequence - removals_.insert(DicomTag(0x0010, 0x0102)); // Patient's Primary Language Modifier Code Sequence - removals_.insert(DicomTag(0x0010, 0x1000)); // Other Patient IDs - removals_.insert(DicomTag(0x0010, 0x1001)); // Other Patient Names - removals_.insert(DicomTag(0x0010, 0x1002)); // Other Patient IDs Sequence - removals_.insert(DicomTag(0x0010, 0x1005)); // Patient's Birth Name - removals_.insert(DicomTag(0x0010, 0x1010)); // Patient's Age - removals_.insert(DicomTag(0x0010, 0x1020)); // Patient's Size - removals_.insert(DicomTag(0x0010, 0x1030)); // Patient's Weight - removals_.insert(DicomTag(0x0010, 0x1040)); // Patient Address - removals_.insert(DicomTag(0x0010, 0x1050)); // Insurance Plan Identification - removals_.insert(DicomTag(0x0010, 0x1060)); // Patient's Mother's Birth Name - removals_.insert(DicomTag(0x0010, 0x1080)); // Military Rank - removals_.insert(DicomTag(0x0010, 0x1081)); // Branch of Service - removals_.insert(DicomTag(0x0010, 0x1090)); // Medical Record Locator - removals_.insert(DicomTag(0x0010, 0x1100)); // Referenced Patient Photo Sequence - removals_.insert(DicomTag(0x0010, 0x2000)); // Medical Alerts - removals_.insert(DicomTag(0x0010, 0x2110)); // Allergies - removals_.insert(DicomTag(0x0010, 0x2150)); // Country of Residence - removals_.insert(DicomTag(0x0010, 0x2152)); // Region of Residence - removals_.insert(DicomTag(0x0010, 0x2154)); // Patient's Telephone Numbers - removals_.insert(DicomTag(0x0010, 0x2155)); // Patient's Telecom Information - removals_.insert(DicomTag(0x0010, 0x2160)); // Ethnic Group - removals_.insert(DicomTag(0x0010, 0x2180)); // Occupation - removals_.insert(DicomTag(0x0010, 0x21a0)); // Smoking Status - removals_.insert(DicomTag(0x0010, 0x21b0)); // Additional Patient's History - removals_.insert(DicomTag(0x0010, 0x21c0)); // Pregnancy Status - removals_.insert(DicomTag(0x0010, 0x21d0)); // Last Menstrual Date - removals_.insert(DicomTag(0x0010, 0x21f0)); // Patient's Religious Preference - removals_.insert(DicomTag(0x0010, 0x2203)); /* X/Z */ // Patient Sex Neutered - removals_.insert(DicomTag(0x0010, 0x2297)); // Responsible Person - removals_.insert(DicomTag(0x0010, 0x2299)); // Responsible Organization - removals_.insert(DicomTag(0x0010, 0x4000)); // Patient Comments - removals_.insert(DicomTag(0x0018, 0x1000)); /* X/Z/D */ // Device Serial Number - removals_.insert(DicomTag(0x0018, 0x1002)); /* TODO UID */ // Device UID - removals_.insert(DicomTag(0x0018, 0x1004)); // Plate ID - removals_.insert(DicomTag(0x0018, 0x1005)); // Generator ID - removals_.insert(DicomTag(0x0018, 0x1007)); // Cassette ID - removals_.insert(DicomTag(0x0018, 0x1008)); // Gantry ID - removals_.insert(DicomTag(0x0018, 0x1030)); /* X/D */ // Protocol Name - removals_.insert(DicomTag(0x0018, 0x1400)); /* X/D */ // Acquisition Device Processing Description - removals_.insert(DicomTag(0x0018, 0x2042)); /* TODO UID */ // Target UID - removals_.insert(DicomTag(0x0018, 0x4000)); // Acquisition Comments - removals_.insert(DicomTag(0x0018, 0x700a)); /* X/D */ // Detector ID - removals_.insert(DicomTag(0x0018, 0x9424)); // Acquisition Protocol Description - removals_.insert(DicomTag(0x0018, 0x9516)); /* X/D */ // Start Acquisition DateTime - removals_.insert(DicomTag(0x0018, 0x9517)); /* X/D */ // End Acquisition DateTime - removals_.insert(DicomTag(0x0018, 0xa003)); // Contribution Description - removals_.insert(DicomTag(0x0020, 0x0200)); /* TODO UID */ // Synchronization Frame of Reference UID - removals_.insert(DicomTag(0x0020, 0x3401)); // Modifying Device ID - removals_.insert(DicomTag(0x0020, 0x3404)); // Modifying Device Manufacturer - removals_.insert(DicomTag(0x0020, 0x3406)); // Modified Image Description - removals_.insert(DicomTag(0x0020, 0x4000)); // Image Comments - removals_.insert(DicomTag(0x0020, 0x9158)); // Frame Comments - removals_.insert(DicomTag(0x0020, 0x9161)); /* TODO UID */ // Concatenation UID - removals_.insert(DicomTag(0x0020, 0x9164)); /* TODO UID */ // Dimension Organization UID - removals_.insert(DicomTag(0x0028, 0x1199)); /* TODO UID */ // Palette Color Lookup Table UID - removals_.insert(DicomTag(0x0028, 0x1214)); /* TODO UID */ // Large Palette Color Lookup Table UID - removals_.insert(DicomTag(0x0028, 0x4000)); // Image Presentation Comments - removals_.insert(DicomTag(0x0032, 0x0012)); // Study ID Issuer - removals_.insert(DicomTag(0x0032, 0x1020)); // Scheduled Study Location - removals_.insert(DicomTag(0x0032, 0x1021)); // Scheduled Study Location AE Title - removals_.insert(DicomTag(0x0032, 0x1030)); // Reason for Study - removals_.insert(DicomTag(0x0032, 0x1032)); // Requesting Physician - removals_.insert(DicomTag(0x0032, 0x1033)); // Requesting Service - removals_.insert(DicomTag(0x0032, 0x1060)); /* X/Z */ // Requested Procedure Description - removals_.insert(DicomTag(0x0032, 0x1070)); // Requested Contrast Agent - removals_.insert(DicomTag(0x0032, 0x4000)); // Study Comments - removals_.insert(DicomTag(0x0038, 0x0004)); // Referenced Patient Alias Sequence - removals_.insert(DicomTag(0x0038, 0x0010)); // Admission ID - removals_.insert(DicomTag(0x0038, 0x0011)); // Issuer of Admission ID - removals_.insert(DicomTag(0x0038, 0x001e)); // Scheduled Patient Institution Residence - removals_.insert(DicomTag(0x0038, 0x0020)); // Admitting Date - removals_.insert(DicomTag(0x0038, 0x0021)); // Admitting Time - removals_.insert(DicomTag(0x0038, 0x0040)); // Discharge Diagnosis Description - removals_.insert(DicomTag(0x0038, 0x0050)); // Special Needs - removals_.insert(DicomTag(0x0038, 0x0060)); // Service Episode ID - removals_.insert(DicomTag(0x0038, 0x0061)); // Issuer of Service Episode ID - removals_.insert(DicomTag(0x0038, 0x0062)); // Service Episode Description - removals_.insert(DicomTag(0x0038, 0x0300)); // Current Patient Location - removals_.insert(DicomTag(0x0038, 0x0400)); // Patient's Institution Residence - removals_.insert(DicomTag(0x0038, 0x0500)); // Patient State - removals_.insert(DicomTag(0x0038, 0x4000)); // Visit Comments - removals_.insert(DicomTag(0x0040, 0x0001)); // Scheduled Station AE Title - removals_.insert(DicomTag(0x0040, 0x0002)); // Scheduled Procedure Step Start Date - removals_.insert(DicomTag(0x0040, 0x0003)); // Scheduled Procedure Step Start Time - removals_.insert(DicomTag(0x0040, 0x0004)); // Scheduled Procedure Step End Date - removals_.insert(DicomTag(0x0040, 0x0005)); // Scheduled Procedure Step End Time - removals_.insert(DicomTag(0x0040, 0x0006)); // Scheduled Performing Physician Name - removals_.insert(DicomTag(0x0040, 0x0007)); // Scheduled Procedure Step Description - removals_.insert(DicomTag(0x0040, 0x000b)); // Scheduled Performing Physician Identification Sequence - removals_.insert(DicomTag(0x0040, 0x0010)); // Scheduled Station Name - removals_.insert(DicomTag(0x0040, 0x0011)); // Scheduled Procedure Step Location - removals_.insert(DicomTag(0x0040, 0x0012)); // Pre-Medication - removals_.insert(DicomTag(0x0040, 0x0241)); // Performed Station AE Title - removals_.insert(DicomTag(0x0040, 0x0242)); // Performed Station Name - removals_.insert(DicomTag(0x0040, 0x0243)); // Performed Location - removals_.insert(DicomTag(0x0040, 0x0244)); // Performed Procedure Step Start Date - removals_.insert(DicomTag(0x0040, 0x0245)); // Performed Procedure Step Start Time - removals_.insert(DicomTag(0x0040, 0x0250)); // Performed Procedure Step End Date - removals_.insert(DicomTag(0x0040, 0x0251)); // Performed Procedure Step End Time - removals_.insert(DicomTag(0x0040, 0x0253)); // Performed Procedure Step ID - removals_.insert(DicomTag(0x0040, 0x0254)); // Performed Procedure Step Description - removals_.insert(DicomTag(0x0040, 0x0275)); // Request Attributes Sequence - removals_.insert(DicomTag(0x0040, 0x0280)); // Comments on the Performed Procedure Step - removals_.insert(DicomTag(0x0040, 0x0555)); // Acquisition Context Sequence - removals_.insert(DicomTag(0x0040, 0x1001)); // Requested Procedure ID - removals_.insert(DicomTag(0x0040, 0x1004)); // Patient Transport Arrangements - removals_.insert(DicomTag(0x0040, 0x1005)); // Requested Procedure Location - removals_.insert(DicomTag(0x0040, 0x1010)); // Names of Intended Recipient of Results - removals_.insert(DicomTag(0x0040, 0x1011)); // Intended Recipients of Results Identification Sequence - removals_.insert(DicomTag(0x0040, 0x1102)); // Person Address - removals_.insert(DicomTag(0x0040, 0x1103)); // Person's Telephone Numbers - removals_.insert(DicomTag(0x0040, 0x1104)); // Person's Telecom Information - removals_.insert(DicomTag(0x0040, 0x1400)); // Requested Procedure Comments - removals_.insert(DicomTag(0x0040, 0x2001)); // Reason for the Imaging Service Request - removals_.insert(DicomTag(0x0040, 0x2008)); // Order Entered By - removals_.insert(DicomTag(0x0040, 0x2009)); // Order Enterer Location - removals_.insert(DicomTag(0x0040, 0x2010)); // Order Callback Phone Number - removals_.insert(DicomTag(0x0040, 0x2011)); // Order Callback Telecom Information - removals_.insert(DicomTag(0x0040, 0x2400)); // Imaging Service Request Comments - removals_.insert(DicomTag(0x0040, 0x3001)); // Confidentiality Constraint on Patient Data Description - removals_.insert(DicomTag(0x0040, 0x4005)); // Scheduled Procedure Step Start DateTime - removals_.insert(DicomTag(0x0040, 0x4010)); // Scheduled Procedure Step Modification DateTime - removals_.insert(DicomTag(0x0040, 0x4011)); // Expected Completion DateTime - removals_.insert(DicomTag(0x0040, 0x4023)); /* TODO UID */ // Referenced General Purpose Scheduled Procedure Step Transaction UID - removals_.insert(DicomTag(0x0040, 0x4025)); // Scheduled Station Name Code Sequence - removals_.insert(DicomTag(0x0040, 0x4027)); // Scheduled Station Geographic Location Code Sequence - removals_.insert(DicomTag(0x0040, 0x4028)); // Performed Station Name Code Sequence - removals_.insert(DicomTag(0x0040, 0x4030)); // Performed Station Geographic Location Code Sequence - removals_.insert(DicomTag(0x0040, 0x4034)); // Scheduled Human Performers Sequence - removals_.insert(DicomTag(0x0040, 0x4035)); // Actual Human Performers Sequence - removals_.insert(DicomTag(0x0040, 0x4036)); // Human Performers Organization - removals_.insert(DicomTag(0x0040, 0x4037)); // Human Performers Name - removals_.insert(DicomTag(0x0040, 0x4050)); // Performed Procedure Step Start DateTime - removals_.insert(DicomTag(0x0040, 0x4051)); // Performed Procedure Step End DateTime - removals_.insert(DicomTag(0x0040, 0x4052)); // Procedure Step Cancellation DateTime - removals_.insert(DicomTag(0x0040, 0xa027)); // Verifying Organization - removals_.insert(DicomTag(0x0040, 0xa078)); // Author Observer Sequence - removals_.insert(DicomTag(0x0040, 0xa07a)); // Participant Sequence - removals_.insert(DicomTag(0x0040, 0xa07c)); // Custodial Organization Sequence - removals_.insert(DicomTag(0x0040, 0xa124)); /* TODO UID */ // UID - removals_.insert(DicomTag(0x0040, 0xa171)); /* TODO UID */ // Observation UID - removals_.insert(DicomTag(0x0040, 0xa172)); /* TODO UID */ // Referenced Observation UID (Trial) - removals_.insert(DicomTag(0x0040, 0xa192)); // Observation Date (Trial) - removals_.insert(DicomTag(0x0040, 0xa193)); // Observation Time (Trial) - removals_.insert(DicomTag(0x0040, 0xa307)); // Current Observer (Trial) - removals_.insert(DicomTag(0x0040, 0xa352)); // Verbal Source (Trial) - removals_.insert(DicomTag(0x0040, 0xa353)); // Address (Trial) - removals_.insert(DicomTag(0x0040, 0xa354)); // Telephone Number (Trial) - removals_.insert(DicomTag(0x0040, 0xa358)); // Verbal Source Identifier Code Sequence (Trial) - removals_.insert(DicomTag(0x0040, 0xa402)); /* TODO UID */ // Observation Subject UID (Trial) - removals_.insert(DicomTag(0x0040, 0xa730)); // Content Sequence - removals_.insert(DicomTag(0x0040, 0xdb0c)); /* TODO UID */ // Template Extension Organization UID - removals_.insert(DicomTag(0x0040, 0xdb0d)); /* TODO UID */ // Template Extension Creator UID - removals_.insert(DicomTag(0x0062, 0x0021)); /* TODO UID */ // Tracking UID - removals_.insert(DicomTag(0x0070, 0x0086)); // Content Creator's Identification Code Sequence - removals_.insert(DicomTag(0x0070, 0x031a)); /* TODO UID */ // Fiducial UID - removals_.insert(DicomTag(0x0070, 0x1101)); /* TODO UID */ // Presentation Display Collection UID - removals_.insert(DicomTag(0x0070, 0x1102)); /* TODO UID */ // Presentation Sequence Collection UID - removals_.insert(DicomTag(0x0088, 0x0140)); /* TODO UID */ // Storage Media File-set UID - removals_.insert(DicomTag(0x0088, 0x0200)); // Icon Image Sequence(see Note 12) - removals_.insert(DicomTag(0x0088, 0x0904)); // Topic Title - removals_.insert(DicomTag(0x0088, 0x0906)); // Topic Subject - removals_.insert(DicomTag(0x0088, 0x0910)); // Topic Author - removals_.insert(DicomTag(0x0088, 0x0912)); // Topic Keywords - removals_.insert(DicomTag(0x0400, 0x0100)); // Digital Signature UID - removals_.insert(DicomTag(0x0400, 0x0402)); // Referenced Digital Signature Sequence - removals_.insert(DicomTag(0x0400, 0x0403)); // Referenced SOP Instance MAC Sequence - removals_.insert(DicomTag(0x0400, 0x0404)); // MAC - removals_.insert(DicomTag(0x0400, 0x0550)); // Modified Attributes Sequence - removals_.insert(DicomTag(0x0400, 0x0561)); // Original Attributes Sequence - removals_.insert(DicomTag(0x2030, 0x0020)); // Text String - removals_.insert(DicomTag(0x3008, 0x0105)); // Source Serial Number - removals_.insert(DicomTag(0x300a, 0x0013)); /* TODO UID */ // Dose Reference UID - removals_.insert(DicomTag(0x300c, 0x0113)); // Reason for Omission Description - removals_.insert(DicomTag(0x300e, 0x0008)); /* X/Z */ // Reviewer Name - removals_.insert(DicomTag(0x4000, 0x0010)); // Arbitrary - removals_.insert(DicomTag(0x4000, 0x4000)); // Text Comments - removals_.insert(DicomTag(0x4008, 0x0042)); // Results ID Issuer - removals_.insert(DicomTag(0x4008, 0x0102)); // Interpretation Recorder - removals_.insert(DicomTag(0x4008, 0x010a)); // Interpretation Transcriber - removals_.insert(DicomTag(0x4008, 0x010b)); // Interpretation Text - removals_.insert(DicomTag(0x4008, 0x010c)); // Interpretation Author - removals_.insert(DicomTag(0x4008, 0x0111)); // Interpretation Approver Sequence - removals_.insert(DicomTag(0x4008, 0x0114)); // Physician Approving Interpretation - removals_.insert(DicomTag(0x4008, 0x0115)); // Interpretation Diagnosis Description - removals_.insert(DicomTag(0x4008, 0x0118)); // Results Distribution List Sequence - removals_.insert(DicomTag(0x4008, 0x0119)); // Distribution Name - removals_.insert(DicomTag(0x4008, 0x011a)); // Distribution Address - removals_.insert(DicomTag(0x4008, 0x0202)); // Interpretation ID Issuer - removals_.insert(DicomTag(0x4008, 0x0300)); // Impressions - removals_.insert(DicomTag(0x4008, 0x4000)); // Results Comments - removals_.insert(DicomTag(0xfffa, 0xfffa)); // Digital Signatures Sequence - removals_.insert(DicomTag(0xfffc, 0xfffc)); // Data Set Trailing Padding - - // Set the DeidentificationMethod tag - ReplaceInternal(DICOM_TAG_DEIDENTIFICATION_METHOD, ORTHANC_DEIDENTIFICATION_METHOD_2017c); - } - - - void DicomModification::SetupAnonymization(DicomVersion version) - { - isAnonymization_ = true; - - removals_.clear(); - clearings_.clear(); - ClearReplacements(); - removePrivateTags_ = true; - level_ = ResourceType_Patient; - uidMap_.clear(); - privateTagsToKeep_.clear(); - - switch (version) - { - case DicomVersion_2008: - SetupAnonymization2008(); - break; - - case DicomVersion_2017c: - SetupAnonymization2017c(); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - // Set the PatientIdentityRemoved tag - ReplaceInternal(DicomTag(0x0012, 0x0062), "YES"); - - // (*) Choose a random patient name and ID - std::string patientId = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient); - ReplaceInternal(DICOM_TAG_PATIENT_ID, patientId); - ReplaceInternal(DICOM_TAG_PATIENT_NAME, patientId); - } - - void DicomModification::Apply(ParsedDicomFile& toModify) - { - // Check the request - assert(ResourceType_Patient + 1 == ResourceType_Study && - ResourceType_Study + 1 == ResourceType_Series && - ResourceType_Series + 1 == ResourceType_Instance); - - if (IsRemoved(DICOM_TAG_PATIENT_ID) || - IsRemoved(DICOM_TAG_STUDY_INSTANCE_UID) || - IsRemoved(DICOM_TAG_SERIES_INSTANCE_UID) || - IsRemoved(DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest); - } - - - // Sanity checks at the patient level - if (level_ == ResourceType_Patient && !IsReplaced(DICOM_TAG_PATIENT_ID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a patient, her PatientID is required to be modified"); - } - - if (!allowManualIdentifiers_) - { - if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a patient, the StudyInstanceUID cannot be manually modified"); - } - - if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a patient, the SeriesInstanceUID cannot be manually modified"); - } - - if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a patient, the SopInstanceUID cannot be manually modified"); - } - } - - - // Sanity checks at the study level - if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_PATIENT_ID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a study, the parent PatientID cannot be manually modified"); - } - - if (!allowManualIdentifiers_) - { - if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a study, the SeriesInstanceUID cannot be manually modified"); - } - - if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a study, the SopInstanceUID cannot be manually modified"); - } - } - - - // Sanity checks at the series level - if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_PATIENT_ID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a series, the parent PatientID cannot be manually modified"); - } - - if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a series, the parent StudyInstanceUID cannot be manually modified"); - } - - if (!allowManualIdentifiers_) - { - if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying a series, the SopInstanceUID cannot be manually modified"); - } - } - - - // Sanity checks at the instance level - if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_PATIENT_ID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying an instance, the parent PatientID cannot be manually modified"); - } - - if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying an instance, the parent StudyInstanceUID cannot be manually modified"); - } - - if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadRequest, - "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified"); - } - - // (0) Create a summary of the source file, if a custom generator - // is provided - if (identifierGenerator_ != NULL) - { - toModify.ExtractDicomSummary(currentSource_); - } - - // (1) Make sure the relationships are updated with the ids that we force too - // i.e: an RT-STRUCT is referencing its own StudyInstanceUID - if (isAnonymization_ && updateReferencedRelationships_) - { - if (IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - std::string original; - std::string replacement = GetReplacementAsString(DICOM_TAG_STUDY_INSTANCE_UID); - toModify.GetTagValue(original, DICOM_TAG_STUDY_INSTANCE_UID); - RegisterMappedDicomIdentifier(original, replacement, ResourceType_Study); - } - - if (IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - std::string original; - std::string replacement = GetReplacementAsString(DICOM_TAG_SERIES_INSTANCE_UID); - toModify.GetTagValue(original, DICOM_TAG_SERIES_INSTANCE_UID); - RegisterMappedDicomIdentifier(original, replacement, ResourceType_Series); - } - - if (IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - std::string original; - std::string replacement = GetReplacementAsString(DICOM_TAG_SOP_INSTANCE_UID); - toModify.GetTagValue(original, DICOM_TAG_SOP_INSTANCE_UID); - RegisterMappedDicomIdentifier(original, replacement, ResourceType_Instance); - } - } - - - // (2) Remove the private tags, if need be - if (removePrivateTags_) - { - toModify.RemovePrivateTags(privateTagsToKeep_); - } - - // (3) Clear the tags specified by the user - for (SetOfTags::const_iterator it = clearings_.begin(); - it != clearings_.end(); ++it) - { - toModify.Clear(*it, true /* only clear if the tag exists in the original file */); - } - - // (4) Remove the tags specified by the user - for (SetOfTags::const_iterator it = removals_.begin(); - it != removals_.end(); ++it) - { - toModify.Remove(*it); - } - - // (5) Replace the tags - for (Replacements::const_iterator it = replacements_.begin(); - it != replacements_.end(); ++it) - { - toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, - DicomReplaceMode_InsertIfAbsent, privateCreator_); - } - - // (6) Update the DICOM identifiers - if (level_ <= ResourceType_Study && - !IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID)) - { - if (keepStudyInstanceUid_) - { - LOG(WARNING) << "Modifying a study while keeping its original StudyInstanceUID: This should be avoided!"; - } - else - { - MapDicomTags(toModify, ResourceType_Study); - } - } - - if (level_ <= ResourceType_Series && - !IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID)) - { - if (keepSeriesInstanceUid_) - { - LOG(WARNING) << "Modifying a series while keeping its original SeriesInstanceUID: This should be avoided!"; - } - else - { - MapDicomTags(toModify, ResourceType_Series); - } - } - - if (level_ <= ResourceType_Instance && // Always true - !IsReplaced(DICOM_TAG_SOP_INSTANCE_UID)) - { - if (keepSopInstanceUid_) - { - LOG(WARNING) << "Modifying an instance while keeping its original SOPInstanceUID: This should be avoided!"; - } - else - { - MapDicomTags(toModify, ResourceType_Instance); - } - } - - // (7) Update the "referenced" relationships in the case of an anonymization - if (isAnonymization_) - { - RelationshipsVisitor visitor(*this); - - if (updateReferencedRelationships_) - { - toModify.Apply(visitor); - } - else - { - visitor.RemoveRelationships(toModify); - } - } - } - - - static bool IsDatabaseKey(const DicomTag& tag) - { - return (tag == DICOM_TAG_PATIENT_ID || - tag == DICOM_TAG_STUDY_INSTANCE_UID || - tag == DICOM_TAG_SERIES_INSTANCE_UID || - tag == DICOM_TAG_SOP_INSTANCE_UID); - } - - - static void ParseListOfTags(DicomModification& target, - const Json::Value& query, - DicomModification::TagOperation operation, - bool force) - { - if (!query.isArray()) - { - throw OrthancException(ErrorCode_BadRequest); - } - - for (Json::Value::ArrayIndex i = 0; i < query.size(); i++) - { - if (query[i].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadRequest); - } - - std::string name = query[i].asString(); - - DicomTag tag = FromDcmtkBridge::ParseTag(name); - - if (!force && IsDatabaseKey(tag)) - { - throw OrthancException(ErrorCode_BadRequest, - "Marking tag \"" + name + "\" as to be " + - (operation == DicomModification::TagOperation_Keep ? "kept" : "removed") + - " requires the \"Force\" option to be set to true"); - } - - switch (operation) - { - case DicomModification::TagOperation_Keep: - target.Keep(tag); - VLOG(1) << "Keep: " << name << " " << tag; - break; - - case DicomModification::TagOperation_Remove: - target.Remove(tag); - VLOG(1) << "Remove: " << name << " " << tag; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - } - - - static void ParseReplacements(DicomModification& target, - const Json::Value& replacements, - bool force) - { - if (!replacements.isObject()) - { - throw OrthancException(ErrorCode_BadRequest); - } - - Json::Value::Members members = replacements.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - const std::string& name = members[i]; - const Json::Value& value = replacements[name]; - - DicomTag tag = FromDcmtkBridge::ParseTag(name); - - if (!force && IsDatabaseKey(tag)) - { - throw OrthancException(ErrorCode_BadRequest, - "Marking tag \"" + name + "\" as to be replaced " + - "requires the \"Force\" option to be set to true"); - } - - target.Replace(tag, value, false); - - VLOG(1) << "Replace: " << name << " " << tag - << " == " << value.toStyledString(); - } - } - - - static bool GetBooleanValue(const std::string& member, - const Json::Value& json, - bool defaultValue) - { - if (!json.isMember(member)) - { - return defaultValue; - } - else if (json[member].type() == Json::booleanValue) - { - return json[member].asBool(); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat, - "Member \"" + member + "\" should be a Boolean value"); - } - } - - - void DicomModification::ParseModifyRequest(const Json::Value& request) - { - if (!request.isObject()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - bool force = GetBooleanValue("Force", request, false); - - if (GetBooleanValue("RemovePrivateTags", request, false)) - { - SetRemovePrivateTags(true); - } - - if (request.isMember("Remove")) - { - ParseListOfTags(*this, request["Remove"], TagOperation_Remove, force); - } - - if (request.isMember("Replace")) - { - ParseReplacements(*this, request["Replace"], force); - } - - // The "Keep" operation only makes sense for the tags - // StudyInstanceUID, SeriesInstanceUID and SOPInstanceUID. Avoid - // this feature as much as possible, as this breaks the DICOM - // model of the real world, except if you know exactly what - // you're doing! - if (request.isMember("Keep")) - { - ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); - } - - // New in Orthanc 1.6.0 - if (request.isMember("PrivateCreator")) - { - privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator"); - } - } - - - void DicomModification::ParseAnonymizationRequest(bool& patientNameReplaced, - const Json::Value& request) - { - if (!request.isObject()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - bool force = GetBooleanValue("Force", request, false); - - // As of Orthanc 1.3.0, the default anonymization is done - // according to PS 3.15-2017c Table E.1-1 (basic profile) - DicomVersion version = DicomVersion_2017c; - if (request.isMember("DicomVersion")) - { - if (request["DicomVersion"].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - version = StringToDicomVersion(request["DicomVersion"].asString()); - } - } - - SetupAnonymization(version); - - std::string patientName = GetReplacementAsString(DICOM_TAG_PATIENT_NAME); - - if (GetBooleanValue("KeepPrivateTags", request, false)) - { - SetRemovePrivateTags(false); - } - - if (request.isMember("Remove")) - { - ParseListOfTags(*this, request["Remove"], TagOperation_Remove, force); - } - - if (request.isMember("Replace")) - { - ParseReplacements(*this, request["Replace"], force); - } - - if (request.isMember("Keep")) - { - ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force); - } - - patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) && - GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName); - - // New in Orthanc 1.6.0 - if (request.isMember("PrivateCreator")) - { - privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator"); - } - } - - - - - static const char* REMOVE_PRIVATE_TAGS = "RemovePrivateTags"; - static const char* LEVEL = "Level"; - static const char* ALLOW_MANUAL_IDENTIFIERS = "AllowManualIdentifiers"; - static const char* KEEP_STUDY_INSTANCE_UID = "KeepStudyInstanceUID"; - static const char* KEEP_SERIES_INSTANCE_UID = "KeepSeriesInstanceUID"; - static const char* KEEP_SOP_INSTANCE_UID = "KeepSOPInstanceUID"; - static const char* UPDATE_REFERENCED_RELATIONSHIPS = "UpdateReferencedRelationships"; - static const char* IS_ANONYMIZATION = "IsAnonymization"; - static const char* REMOVALS = "Removals"; - static const char* CLEARINGS = "Clearings"; - static const char* PRIVATE_TAGS_TO_KEEP = "PrivateTagsToKeep"; - static const char* REPLACEMENTS = "Replacements"; - static const char* MAP_PATIENTS = "MapPatients"; - static const char* MAP_STUDIES = "MapStudies"; - static const char* MAP_SERIES = "MapSeries"; - static const char* MAP_INSTANCES = "MapInstances"; - static const char* PRIVATE_CREATOR = "PrivateCreator"; // New in Orthanc 1.6.0 - - void DicomModification::Serialize(Json::Value& value) const - { - if (identifierGenerator_ != NULL) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot serialize a DicomModification with a custom identifier generator"); - } - - value = Json::objectValue; - value[REMOVE_PRIVATE_TAGS] = removePrivateTags_; - value[LEVEL] = EnumerationToString(level_); - value[ALLOW_MANUAL_IDENTIFIERS] = allowManualIdentifiers_; - value[KEEP_STUDY_INSTANCE_UID] = keepStudyInstanceUid_; - value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_; - value[KEEP_SOP_INSTANCE_UID] = keepSopInstanceUid_; - value[UPDATE_REFERENCED_RELATIONSHIPS] = updateReferencedRelationships_; - value[IS_ANONYMIZATION] = isAnonymization_; - value[PRIVATE_CREATOR] = privateCreator_; - - SerializationToolbox::WriteSetOfTags(value, removals_, REMOVALS); - SerializationToolbox::WriteSetOfTags(value, clearings_, CLEARINGS); - SerializationToolbox::WriteSetOfTags(value, privateTagsToKeep_, PRIVATE_TAGS_TO_KEEP); - - Json::Value& tmp = value[REPLACEMENTS]; - - tmp = Json::objectValue; - - for (Replacements::const_iterator it = replacements_.begin(); - it != replacements_.end(); ++it) - { - assert(it->second != NULL); - tmp[it->first.Format()] = *it->second; - } - - Json::Value& mapPatients = value[MAP_PATIENTS]; - Json::Value& mapStudies = value[MAP_STUDIES]; - Json::Value& mapSeries = value[MAP_SERIES]; - Json::Value& mapInstances = value[MAP_INSTANCES]; - - mapPatients = Json::objectValue; - mapStudies = Json::objectValue; - mapSeries = Json::objectValue; - mapInstances = Json::objectValue; - - for (UidMap::const_iterator it = uidMap_.begin(); it != uidMap_.end(); ++it) - { - Json::Value* tmp = NULL; - - switch (it->first.first) - { - case ResourceType_Patient: - tmp = &mapPatients; - break; - - case ResourceType_Study: - tmp = &mapStudies; - break; - - case ResourceType_Series: - tmp = &mapSeries; - break; - - case ResourceType_Instance: - tmp = &mapInstances; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - assert(tmp != NULL); - (*tmp) [it->first.second] = it->second; - } - } - - - void DicomModification::UnserializeUidMap(ResourceType level, - const Json::Value& serialized, - const char* field) - { - if (!serialized.isMember(field) || - serialized[field].type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value::Members names = serialized[field].getMemberNames(); - - for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) - { - const Json::Value& value = serialized[field][*it]; - - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - uidMap_[std::make_pair(level, *it)] = value.asString(); - } - } - } - - - DicomModification::DicomModification(const Json::Value& serialized) : - identifierGenerator_(NULL) - { - removePrivateTags_ = SerializationToolbox::ReadBoolean(serialized, REMOVE_PRIVATE_TAGS); - level_ = StringToResourceType(SerializationToolbox::ReadString(serialized, LEVEL).c_str()); - allowManualIdentifiers_ = SerializationToolbox::ReadBoolean(serialized, ALLOW_MANUAL_IDENTIFIERS); - keepStudyInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_STUDY_INSTANCE_UID); - keepSeriesInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SERIES_INSTANCE_UID); - keepSopInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SOP_INSTANCE_UID); - updateReferencedRelationships_ = SerializationToolbox::ReadBoolean - (serialized, UPDATE_REFERENCED_RELATIONSHIPS); - isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); - - if (serialized.isMember(PRIVATE_CREATOR)) - { - privateCreator_ = SerializationToolbox::ReadString(serialized, PRIVATE_CREATOR); - } - - SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); - SerializationToolbox::ReadSetOfTags(clearings_, serialized, CLEARINGS); - SerializationToolbox::ReadSetOfTags(privateTagsToKeep_, serialized, PRIVATE_TAGS_TO_KEEP); - - if (!serialized.isMember(REPLACEMENTS) || - serialized[REPLACEMENTS].type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value::Members names = serialized[REPLACEMENTS].getMemberNames(); - - for (Json::Value::Members::const_iterator it = names.begin(); it != names.end(); ++it) - { - DicomTag tag(0, 0); - if (!DicomTag::ParseHexadecimal(tag, it->c_str())) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - const Json::Value& value = serialized[REPLACEMENTS][*it]; - replacements_.insert(std::make_pair(tag, new Json::Value(value))); - } - } - - UnserializeUidMap(ResourceType_Patient, serialized, MAP_PATIENTS); - UnserializeUidMap(ResourceType_Study, serialized, MAP_STUDIES); - UnserializeUidMap(ResourceType_Series, serialized, MAP_SERIES); - UnserializeUidMap(ResourceType_Instance, serialized, MAP_INSTANCES); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/DicomModification.h --- a/Core/DicomParsing/DicomModification.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,201 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ParsedDicomFile.h" - -namespace Orthanc -{ - class DicomModification : public boost::noncopyable - { - /** - * Process: - * (1) Remove private tags - * (2) Remove tags specified by the user - * (3) Replace tags - **/ - - public: - enum TagOperation - { - TagOperation_Keep, - TagOperation_Remove - }; - - class IDicomIdentifierGenerator : public boost::noncopyable - { - public: - virtual ~IDicomIdentifierGenerator() - { - } - - virtual bool Apply(std::string& target, - const std::string& sourceIdentifier, - ResourceType level, - const DicomMap& sourceDicom) = 0; - }; - - private: - class RelationshipsVisitor; - - typedef std::set SetOfTags; - typedef std::map Replacements; - typedef std::map< std::pair, std::string> UidMap; - - SetOfTags removals_; - SetOfTags clearings_; - Replacements replacements_; - bool removePrivateTags_; - ResourceType level_; - UidMap uidMap_; - SetOfTags privateTagsToKeep_; - bool allowManualIdentifiers_; - bool keepStudyInstanceUid_; - bool keepSeriesInstanceUid_; - bool keepSopInstanceUid_; - bool updateReferencedRelationships_; - bool isAnonymization_; - DicomMap currentSource_; - std::string privateCreator_; - - IDicomIdentifierGenerator* identifierGenerator_; - - std::string MapDicomIdentifier(const std::string& original, - ResourceType level); - - void RegisterMappedDicomIdentifier(const std::string& original, - const std::string& mapped, - ResourceType level); - - void MapDicomTags(ParsedDicomFile& dicom, - ResourceType level); - - void MarkNotOrthancAnonymization(); - - void ClearReplacements(); - - bool CancelReplacement(const DicomTag& tag); - - void ReplaceInternal(const DicomTag& tag, - const Json::Value& value); - - void SetupAnonymization2008(); - - void SetupAnonymization2017c(); - - void UnserializeUidMap(ResourceType level, - const Json::Value& serialized, - const char* field); - - public: - DicomModification(); - - DicomModification(const Json::Value& serialized); - - ~DicomModification(); - - void Keep(const DicomTag& tag); - - void Remove(const DicomTag& tag); - - // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization) - void Clear(const DicomTag& tag); - - bool IsRemoved(const DicomTag& tag) const; - - bool IsCleared(const DicomTag& tag) const; - - // "safeForAnonymization" tells Orthanc that this replacement does - // not break the anonymization process it implements (for internal use only) - void Replace(const DicomTag& tag, - const Json::Value& value, // Encoded using UTF-8 - bool safeForAnonymization); - - bool IsReplaced(const DicomTag& tag) const; - - const Json::Value& GetReplacement(const DicomTag& tag) const; - - std::string GetReplacementAsString(const DicomTag& tag) const; - - void SetRemovePrivateTags(bool removed); - - bool ArePrivateTagsRemoved() const - { - return removePrivateTags_; - } - - void SetLevel(ResourceType level); - - ResourceType GetLevel() const - { - return level_; - } - - void SetupAnonymization(DicomVersion version); - - void Apply(ParsedDicomFile& toModify); - - void SetAllowManualIdentifiers(bool check) - { - allowManualIdentifiers_ = check; - } - - bool AreAllowManualIdentifiers() const - { - return allowManualIdentifiers_; - } - - void ParseModifyRequest(const Json::Value& request); - - void ParseAnonymizationRequest(bool& patientNameReplaced, - const Json::Value& request); - - void SetDicomIdentifierGenerator(IDicomIdentifierGenerator& generator) - { - identifierGenerator_ = &generator; - } - - void Serialize(Json::Value& value) const; - - void SetPrivateCreator(const std::string& privateCreator) - { - privateCreator_ = privateCreator; - } - - const std::string& GetPrivateCreator() - { - return privateCreator_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/DicomWebJsonVisitor.cpp --- a/Core/DicomParsing/DicomWebJsonVisitor.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,673 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "DicomWebJsonVisitor.h" - -#include "../OrthancException.h" -#include "../Toolbox.h" -#include "FromDcmtkBridge.h" - -#include -#include - - -static const char* const KEY_ALPHABETIC = "Alphabetic"; -static const char* const KEY_IDEOGRAPHIC = "Ideographic"; -static const char* const KEY_PHONETIC = "Phonetic"; -static const char* const KEY_BULK_DATA_URI = "BulkDataURI"; -static const char* const KEY_INLINE_BINARY = "InlineBinary"; -static const char* const KEY_SQ = "SQ"; -static const char* const KEY_TAG = "tag"; -static const char* const KEY_VALUE = "Value"; -static const char* const KEY_VR = "vr"; - - -namespace Orthanc -{ -#if ORTHANC_ENABLE_PUGIXML == 1 - static void DecomposeXmlPersonName(pugi::xml_node& target, - const std::string& source) - { - std::vector tokens; - Toolbox::TokenizeString(tokens, source, '^'); - - if (tokens.size() >= 1) - { - target.append_child("FamilyName").text() = tokens[0].c_str(); - } - - if (tokens.size() >= 2) - { - target.append_child("GivenName").text() = tokens[1].c_str(); - } - - if (tokens.size() >= 3) - { - target.append_child("MiddleName").text() = tokens[2].c_str(); - } - - if (tokens.size() >= 4) - { - target.append_child("NamePrefix").text() = tokens[3].c_str(); - } - - if (tokens.size() >= 5) - { - target.append_child("NameSuffix").text() = tokens[4].c_str(); - } - } - - static void ExploreXmlDataset(pugi::xml_node& target, - const Json::Value& source) - { - // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.3.html#table_F.3.1-1 - assert(source.type() == Json::objectValue); - - Json::Value::Members members = source.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - const DicomTag tag = FromDcmtkBridge::ParseTag(members[i]); - const Json::Value& content = source[members[i]]; - - assert(content.type() == Json::objectValue && - content.isMember(KEY_VR) && - content[KEY_VR].type() == Json::stringValue); - const std::string vr = content[KEY_VR].asString(); - - const std::string keyword = FromDcmtkBridge::GetTagName(tag, ""); - - pugi::xml_node node = target.append_child("DicomAttribute"); - node.append_attribute(KEY_TAG).set_value(members[i].c_str()); - node.append_attribute(KEY_VR).set_value(vr.c_str()); - - if (keyword != std::string(DcmTag_ERROR_TagName)) - { - node.append_attribute("keyword").set_value(keyword.c_str()); - } - - if (content.isMember(KEY_VALUE)) - { - assert(content[KEY_VALUE].type() == Json::arrayValue); - - for (Json::Value::ArrayIndex j = 0; j < content[KEY_VALUE].size(); j++) - { - std::string number = boost::lexical_cast(j + 1); - - if (vr == "SQ") - { - if (content[KEY_VALUE][j].type() == Json::objectValue) - { - pugi::xml_node child = node.append_child("Item"); - child.append_attribute("number").set_value(number.c_str()); - ExploreXmlDataset(child, content[KEY_VALUE][j]); - } - } - if (vr == "PN") - { - bool hasAlphabetic = (content[KEY_VALUE][j].isMember(KEY_ALPHABETIC) && - content[KEY_VALUE][j][KEY_ALPHABETIC].type() == Json::stringValue); - - bool hasIdeographic = (content[KEY_VALUE][j].isMember(KEY_IDEOGRAPHIC) && - content[KEY_VALUE][j][KEY_IDEOGRAPHIC].type() == Json::stringValue); - - bool hasPhonetic = (content[KEY_VALUE][j].isMember(KEY_PHONETIC) && - content[KEY_VALUE][j][KEY_PHONETIC].type() == Json::stringValue); - - if (hasAlphabetic || - hasIdeographic || - hasPhonetic) - { - pugi::xml_node child = node.append_child("PersonName"); - child.append_attribute("number").set_value(number.c_str()); - - if (hasAlphabetic) - { - pugi::xml_node name = child.append_child(KEY_ALPHABETIC); - DecomposeXmlPersonName(name, content[KEY_VALUE][j][KEY_ALPHABETIC].asString()); - } - - if (hasIdeographic) - { - pugi::xml_node name = child.append_child(KEY_IDEOGRAPHIC); - DecomposeXmlPersonName(name, content[KEY_VALUE][j][KEY_IDEOGRAPHIC].asString()); - } - - if (hasPhonetic) - { - pugi::xml_node name = child.append_child(KEY_PHONETIC); - DecomposeXmlPersonName(name, content[KEY_VALUE][j][KEY_PHONETIC].asString()); - } - } - } - else - { - pugi::xml_node child = node.append_child("Value"); - child.append_attribute("number").set_value(number.c_str()); - - switch (content[KEY_VALUE][j].type()) - { - case Json::stringValue: - child.text() = content[KEY_VALUE][j].asCString(); - break; - - case Json::realValue: - child.text() = content[KEY_VALUE][j].asFloat(); - break; - - case Json::intValue: - child.text() = content[KEY_VALUE][j].asInt(); - break; - - case Json::uintValue: - child.text() = content[KEY_VALUE][j].asUInt(); - break; - - default: - break; - } - } - } - } - else if (content.isMember(KEY_BULK_DATA_URI) && - content[KEY_BULK_DATA_URI].type() == Json::stringValue) - { - pugi::xml_node child = node.append_child("BulkData"); - child.append_attribute("URI").set_value(content[KEY_BULK_DATA_URI].asCString()); - } - else if (content.isMember(KEY_INLINE_BINARY) && - content[KEY_INLINE_BINARY].type() == Json::stringValue) - { - pugi::xml_node child = node.append_child("InlineBinary"); - child.text() = content[KEY_INLINE_BINARY].asCString(); - } - } - } -#endif - - -#if ORTHANC_ENABLE_PUGIXML == 1 - static void DicomWebJsonToXml(pugi::xml_document& target, - const Json::Value& source) - { - pugi::xml_node root = target.append_child("NativeDicomModel"); - root.append_attribute("xmlns").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM"); - root.append_attribute("xsi:schemaLocation").set_value("http://dicom.nema.org/PS3.19/models/NativeDICOM"); - root.append_attribute("xmlns:xsi").set_value("http://www.w3.org/2001/XMLSchema-instance"); - - ExploreXmlDataset(root, source); - - pugi::xml_node decl = target.prepend_child(pugi::node_declaration); - decl.append_attribute("version").set_value("1.0"); - decl.append_attribute("encoding").set_value("utf-8"); - } -#endif - - - std::string DicomWebJsonVisitor::FormatTag(const DicomTag& tag) - { - char buf[16]; - sprintf(buf, "%04X%04X", tag.GetGroup(), tag.GetElement()); - return std::string(buf); - } - - - Json::Value& DicomWebJsonVisitor::CreateNode(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag) - { - assert(parentTags.size() == parentIndexes.size()); - - Json::Value* node = &result_; - - for (size_t i = 0; i < parentTags.size(); i++) - { - std::string t = FormatTag(parentTags[i]); - - if (!node->isMember(t)) - { - Json::Value item = Json::objectValue; - item[KEY_VR] = KEY_SQ; - item[KEY_VALUE] = Json::arrayValue; - item[KEY_VALUE].append(Json::objectValue); - (*node) [t] = item; - - node = &(*node)[t][KEY_VALUE][0]; - } - else if ((*node) [t].type() != Json::objectValue || - !(*node) [t].isMember(KEY_VR) || - (*node) [t][KEY_VR].type() != Json::stringValue || - (*node) [t][KEY_VR].asString() != KEY_SQ || - !(*node) [t].isMember(KEY_VALUE) || - (*node) [t][KEY_VALUE].type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - size_t currentSize = (*node) [t][KEY_VALUE].size(); - - if (parentIndexes[i] < currentSize) - { - // The node already exists - } - else if (parentIndexes[i] == currentSize) - { - (*node) [t][KEY_VALUE].append(Json::objectValue); - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - - node = &(*node) [t][KEY_VALUE][Json::ArrayIndex(parentIndexes[i])]; - } - } - - assert(node->type() == Json::objectValue); - - std::string t = FormatTag(tag); - if (node->isMember(t)) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - (*node) [t] = Json::objectValue; - return (*node) [t]; - } - } - - - Json::Value DicomWebJsonVisitor::FormatInteger(int64_t value) - { - if (value < 0) - { - return Json::Value(static_cast(value)); - } - else - { - return Json::Value(static_cast(value)); - } - } - - - Json::Value DicomWebJsonVisitor::FormatDouble(double value) - { - try - { - long long a = boost::math::llround(value); - - double d = fabs(value - static_cast(a)); - - if (d <= std::numeric_limits::epsilon() * 100.0) - { - return FormatInteger(a); - } - else - { - return Json::Value(value); - } - } - catch (boost::math::rounding_error&) - { - // Can occur if "long long" is too small to receive this value - // (e.g. infinity) - return Json::Value(value); - } - } - - -#if ORTHANC_ENABLE_PUGIXML == 1 - void DicomWebJsonVisitor::FormatXml(std::string& target) const - { - pugi::xml_document doc; - DicomWebJsonToXml(doc, result_); - Toolbox::XmlToString(target, doc); - } -#endif - - - void DicomWebJsonVisitor::VisitEmptySequence(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag) - { - if (tag.GetElement() != 0x0000) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(ValueRepresentation_Sequence); - } - } - - - void DicomWebJsonVisitor::VisitBinary(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const void* data, - size_t size) - { - assert(vr == ValueRepresentation_OtherByte || - vr == ValueRepresentation_OtherDouble || - vr == ValueRepresentation_OtherFloat || - vr == ValueRepresentation_OtherLong || - vr == ValueRepresentation_OtherWord || - vr == ValueRepresentation_Unknown); - - if (tag.GetElement() != 0x0000) - { - BinaryMode mode; - std::string bulkDataUri; - - if (formatter_ == NULL) - { - mode = BinaryMode_InlineBinary; - } - else - { - mode = formatter_->Format(bulkDataUri, parentTags, parentIndexes, tag, vr); - } - - if (mode != BinaryMode_Ignore) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(vr); - - switch (mode) - { - case BinaryMode_BulkDataUri: - node[KEY_BULK_DATA_URI] = bulkDataUri; - break; - - case BinaryMode_InlineBinary: - { - std::string tmp(static_cast(data), size); - - std::string base64; - Toolbox::EncodeBase64(base64, tmp); - - node[KEY_INLINE_BINARY] = base64; - break; - } - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - } - } - - - void DicomWebJsonVisitor::VisitIntegers(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector& values) - { - if (tag.GetElement() != 0x0000 && - vr != ValueRepresentation_NotSupported) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(vr); - - if (!values.empty()) - { - Json::Value content = Json::arrayValue; - for (size_t i = 0; i < values.size(); i++) - { - content.append(FormatInteger(values[i])); - } - - node[KEY_VALUE] = content; - } - } - } - - void DicomWebJsonVisitor::VisitDoubles(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector& values) - { - if (tag.GetElement() != 0x0000 && - vr != ValueRepresentation_NotSupported) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(vr); - - if (!values.empty()) - { - Json::Value content = Json::arrayValue; - for (size_t i = 0; i < values.size(); i++) - { - content.append(FormatDouble(values[i])); - } - - node[KEY_VALUE] = content; - } - } - } - - - void DicomWebJsonVisitor::VisitAttributes(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - const std::vector& values) - { - if (tag.GetElement() != 0x0000) - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(ValueRepresentation_AttributeTag); - - if (!values.empty()) - { - Json::Value content = Json::arrayValue; - for (size_t i = 0; i < values.size(); i++) - { - content.append(FormatTag(values[i])); - } - - node[KEY_VALUE] = content; - } - } - } - - - ITagVisitor::Action - DicomWebJsonVisitor::VisitString(std::string& newValue, - const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::string& value) - { - if (tag.GetElement() == 0x0000 || - vr == ValueRepresentation_NotSupported) - { - return Action_None; - } - else - { - Json::Value& node = CreateNode(parentTags, parentIndexes, tag); - node[KEY_VR] = EnumerationToString(vr); - -#if 0 - /** - * TODO - The JSON file has an UTF-8 encoding, thus DCMTK - * replaces the specific character set with "ISO_IR 192" - * (UNICODE UTF-8). On Google Cloud Healthcare, however, the - * source encoding is reported, which seems more logical. We - * thus choose the Google convention. Enabling this block will - * mimic the DCMTK behavior. - **/ - if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) - { - node[KEY_VALUE].append("ISO_IR 192"); - } - else -#endif - { - std::string truncated; - - if (!value.empty() && - value[value.size() - 1] == '\0') - { - truncated = value.substr(0, value.size() - 1); - } - else - { - truncated = value; - } - - if (!truncated.empty()) - { - std::vector tokens; - Toolbox::TokenizeString(tokens, truncated, '\\'); - - if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET && - tokens.size() > 1 && - tokens[0].empty()) - { - // Specific character set with code extension: Remove the - // first element from the vector of encodings - tokens.erase(tokens.begin()); - } - - node[KEY_VALUE] = Json::arrayValue; - for (size_t i = 0; i < tokens.size(); i++) - { - try - { - switch (vr) - { - case ValueRepresentation_PersonName: - { - Json::Value value = Json::objectValue; - if (!tokens[i].empty()) - { - std::vector components; - Toolbox::TokenizeString(components, tokens[i], '='); - - if (components.size() >= 1) - { - value[KEY_ALPHABETIC] = components[0]; - } - - if (components.size() >= 2) - { - value[KEY_IDEOGRAPHIC] = components[1]; - } - - if (components.size() >= 3) - { - value[KEY_PHONETIC] = components[2]; - } - } - - node[KEY_VALUE].append(value); - break; - } - - case ValueRepresentation_IntegerString: - { - /** - * The calls to "StripSpaces()" below fix the - * issue reported by Rana Asim Wajid on 2019-06-05 - * ("Error Exception while invoking plugin service - * 32: Bad file format"): - * https://groups.google.com/d/msg/orthanc-users/T32FovWPcCE/-hKFbfRJBgAJ - **/ - - std::string t = Orthanc::Toolbox::StripSpaces(tokens[i]); - if (t.empty()) - { - node[KEY_VALUE].append(Json::nullValue); - } - else - { - int64_t value = boost::lexical_cast(t); - node[KEY_VALUE].append(FormatInteger(value)); - } - - break; - } - - case ValueRepresentation_DecimalString: - { - std::string t = Orthanc::Toolbox::StripSpaces(tokens[i]); - if (t.empty()) - { - node[KEY_VALUE].append(Json::nullValue); - } - else - { - double value = boost::lexical_cast(t); - node[KEY_VALUE].append(FormatDouble(value)); - } - - break; - } - - default: - if (tokens[i].empty()) - { - node[KEY_VALUE].append(Json::nullValue); - } - else - { - node[KEY_VALUE].append(tokens[i]); - } - - break; - } - } - catch (boost::bad_lexical_cast&) - { - std::string tmp; - if (value.size() < 64 && - Toolbox::IsAsciiString(value)) - { - tmp = ": " + value; - } - - LOG(WARNING) << "Ignoring DICOM tag (" << tag.Format() - << ") with invalid content for VR " << EnumerationToString(vr) << tmp; - } - } - } - } - } - - return Action_None; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/DicomWebJsonVisitor.h --- a/Core/DicomParsing/DicomWebJsonVisitor.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_PUGIXML) -# error Macro ORTHANC_ENABLE_PUGIXML must be defined to use this file -#endif - -#include "ITagVisitor.h" - -#include - - -namespace Orthanc -{ - class DicomWebJsonVisitor : public ITagVisitor - { - public: - enum BinaryMode - { - BinaryMode_Ignore, - BinaryMode_BulkDataUri, - BinaryMode_InlineBinary - }; - - class IBinaryFormatter : public boost::noncopyable - { - public: - virtual ~IBinaryFormatter() - { - } - - virtual BinaryMode Format(std::string& bulkDataUri, - const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr) = 0; - }; - - private: - Json::Value result_; - IBinaryFormatter *formatter_; - - static std::string FormatTag(const DicomTag& tag); - - Json::Value& CreateNode(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag); - - static Json::Value FormatInteger(int64_t value); - - static Json::Value FormatDouble(double value); - - public: - DicomWebJsonVisitor() : - formatter_(NULL) - { - Clear(); - } - - void SetFormatter(IBinaryFormatter& formatter) - { - formatter_ = &formatter; - } - - void Clear() - { - result_ = Json::objectValue; - } - - const Json::Value& GetResult() const - { - return result_; - } - -#if ORTHANC_ENABLE_PUGIXML == 1 - void FormatXml(std::string& target) const; -#endif - - virtual void VisitNotSupported(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr) - ORTHANC_OVERRIDE - { - } - - virtual void VisitEmptySequence(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag) - ORTHANC_OVERRIDE; - - virtual void VisitBinary(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const void* data, - size_t size) - ORTHANC_OVERRIDE; - - virtual void VisitIntegers(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector& values) - ORTHANC_OVERRIDE; - - virtual void VisitDoubles(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector& values) - ORTHANC_OVERRIDE; - - virtual void VisitAttributes(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - const std::vector& values) - ORTHANC_OVERRIDE; - - virtual Action VisitString(std::string& newValue, - const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::string& value) - ORTHANC_OVERRIDE; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/FromDcmtkBridge.cpp --- a/Core/DicomParsing/FromDcmtkBridge.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2676 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if !defined(DCMTK_VERSION_NUMBER) -# error The macro DCMTK_VERSION_NUMBER must be defined -#endif - -#include "FromDcmtkBridge.h" -#include "ToDcmtkBridge.h" -#include "../Compatibility.h" -#include "../Logging.h" -#include "../Toolbox.h" -#include "../OrthancException.h" - -#if ORTHANC_SANDBOXED == 0 -# include "../TemporaryFile.h" -#endif - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if DCMTK_VERSION_NUMBER >= 361 -# include -# include -#endif - -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 -# include -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 -# include -# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 -# include -# endif -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 -# include -# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 -# include -# endif -#endif - - -#include -#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 -# include -# include // include to support color images -#endif - - -namespace Orthanc -{ - static bool IsBinaryTag(const DcmTag& key) - { - return (key.isUnknownVR() || - key.getEVR() == EVR_OB || - key.getEVR() == EVR_OW || - key.getEVR() == EVR_UN || - key.getEVR() == EVR_ox); - } - - -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 - static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary, - FrameworkResources::FileResourceId resource) - { - std::string content; - FrameworkResources::GetFileResource(content, resource); - -#if ORTHANC_SANDBOXED == 0 - TemporaryFile tmp; - tmp.Write(content); - - if (!dictionary.loadDictionary(tmp.GetPath().c_str())) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot read embedded dictionary. Under Windows, make sure that " - "your TEMP directory does not contain special characters."); - } -#else - if (!dictionary.loadFromMemory(content)) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot read embedded dictionary. Under Windows, make sure that " - "your TEMP directory does not contain special characters."); - } -#endif - } -#endif - - - namespace - { - class DictionaryLocker : public boost::noncopyable - { - private: - DcmDataDictionary& dictionary_; - - public: - DictionaryLocker() : dictionary_(dcmDataDict.wrlock()) - { - } - - ~DictionaryLocker() - { -#if DCMTK_VERSION_NUMBER >= 364 - dcmDataDict.wrunlock(); -#else - dcmDataDict.unlock(); -#endif - } - - DcmDataDictionary& operator*() - { - return dictionary_; - } - - DcmDataDictionary* operator->() - { - return &dictionary_; - } - }; - - -#define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter) \ - \ - struct converter \ - { \ - typedef cType CType; \ - \ - static bool Apply(CType& result, \ - DcmElement& element, \ - size_t i) \ - { \ - return dynamic_cast(element).getter(result, i).good(); \ - } \ - }; - -DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint32Converter, Sint32, DcmSignedLong, getSint32) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint16Converter, Sint16, DcmSignedShort, getSint16) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint32Converter, Uint32, DcmUnsignedLong, getUint32) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToUint16Converter, Uint16, DcmUnsignedShort, getUint16) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat32Converter, Float32, DcmFloatingPointSingle, getFloat32) -DCMTK_TO_CTYPE_CONVERTER(DcmtkToFloat64Converter, Float64, DcmFloatingPointDouble, getFloat64) - - - template - static DicomValue* ApplyDcmtkToCTypeConverter(DcmElement& element) - { - F f; - typename F::CType value; - - if (element.getLength() > sizeof(typename F::CType) - && (element.getLength() % sizeof(typename F::CType)) == 0) - { - size_t count = element.getLength() / sizeof(typename F::CType); - std::vector strings; - for (size_t i = 0; i < count; i++) { - if (f.Apply(value, element, i)) { - strings.push_back(boost::lexical_cast(value)); - } - } - return new DicomValue(boost::algorithm::join(strings, "\\"), false); - } - else if (f.Apply(value, element, 0)) { - return new DicomValue(boost::lexical_cast(value), false); - } - else { - return new DicomValue; - } - } - - } - - - void FromDcmtkBridge::InitializeDictionary(bool loadPrivateDictionary) - { - LOG(INFO) << "Using DCTMK version: " << DCMTK_VERSION_NUMBER; - - { - DictionaryLocker locker; - - locker->clear(); - -#if DCMTK_USE_EMBEDDED_DICTIONARIES == 1 - LOG(INFO) << "Loading the embedded dictionaries"; - /** - * Do not load DICONDE dictionary, it breaks the other tags. The - * command "strace storescu 2>&1 |grep dic" shows that DICONDE - * dictionary is not loaded by storescu. - **/ - //LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICONDE); - - LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_DICOM); - - if (loadPrivateDictionary) - { - LOG(INFO) << "Loading the embedded dictionary of private tags"; - LoadEmbeddedDictionary(*locker, FrameworkResources::DICTIONARY_PRIVATE); - } - else - { - LOG(INFO) << "The dictionary of private tags has not been loaded"; - } - -#else - std::vector dictionaries; - - const char* env = std::getenv(DCM_DICT_ENVIRONMENT_VARIABLE); - if (env != NULL) - { - // This mimics the behavior of DCMTK: - // https://support.dcmtk.org/docs/file_envvars.html -#if defined(_WIN32) - Toolbox::TokenizeString(dictionaries, std::string(env), ';'); -#else - Toolbox::TokenizeString(dictionaries, std::string(env), ':'); -#endif - } - else - { - boost::filesystem::path base = DCMTK_DICTIONARY_DIR; - dictionaries.push_back((base / "dicom.dic").string()); - dictionaries.push_back((base / "private.dic").string()); - } - - for (size_t i = 0; i < dictionaries.size(); i++) - { - LOG(WARNING) << "Loading external DICOM dictionary: \"" << dictionaries[i] << "\""; - - if (!locker->loadDictionary(dictionaries[i].c_str())) - { - throw OrthancException(ErrorCode_InexistentFile); - } - } - -#endif - } - - /* make sure data dictionary is loaded */ - if (!dcmDataDict.isDictionaryLoaded()) - { - throw OrthancException(ErrorCode_InternalError, - "No DICOM dictionary loaded, check environment variable: " + - std::string(DCM_DICT_ENVIRONMENT_VARIABLE)); - } - - { - // Test the dictionary with a simple DICOM tag - DcmTag key(0x0010, 0x1030); // This is PatientWeight - if (key.getEVR() != EVR_DS) - { - throw OrthancException(ErrorCode_InternalError, - "The DICOM dictionary has not been correctly read"); - } - } - } - - - void FromDcmtkBridge::RegisterDictionaryTag(const DicomTag& tag, - ValueRepresentation vr, - const std::string& name, - unsigned int minMultiplicity, - unsigned int maxMultiplicity, - const std::string& privateCreator) - { - if (minMultiplicity < 1) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - bool arbitrary = false; - if (maxMultiplicity == 0) - { - maxMultiplicity = DcmVariableVM; - arbitrary = true; - } - else if (maxMultiplicity < minMultiplicity) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - DcmEVR evr = ToDcmtkBridge::Convert(vr); - - LOG(INFO) << "Registering tag in dictionary: " << tag << " " << (DcmVR(evr).getValidVRName()) << " " - << name << " (multiplicity: " << minMultiplicity << "-" - << (arbitrary ? "n" : boost::lexical_cast(maxMultiplicity)) << ")"; - - std::unique_ptr entry; - if (privateCreator.empty()) - { - if (tag.GetGroup() % 2 == 1) - { - char buf[128]; - sprintf(buf, "Warning: You are registering a private tag (%04x,%04x), " - "but no private creator was associated with it", - tag.GetGroup(), tag.GetElement()); - LOG(WARNING) << buf; - } - - entry.reset(new DcmDictEntry(tag.GetGroup(), - tag.GetElement(), - evr, name.c_str(), - static_cast(minMultiplicity), - static_cast(maxMultiplicity), - NULL /* version */, - OFTrue /* doCopyString */, - NULL /* private creator */)); - } - else - { - // "Private Data Elements have an odd Group Number that is not - // (0001,eeee), (0003,eeee), (0005,eeee), (0007,eeee), or - // (FFFF,eeee)." - if (tag.GetGroup() % 2 == 0 /* even */ || - tag.GetGroup() == 0x0001 || - tag.GetGroup() == 0x0003 || - tag.GetGroup() == 0x0005 || - tag.GetGroup() == 0x0007 || - tag.GetGroup() == 0xffff) - { - char buf[128]; - sprintf(buf, "Trying to register private tag (%04x,%04x), but it must have an odd group >= 0x0009", - tag.GetGroup(), tag.GetElement()); - throw OrthancException(ErrorCode_ParameterOutOfRange, std::string(buf)); - } - - entry.reset(new DcmDictEntry(tag.GetGroup(), - tag.GetElement(), - evr, name.c_str(), - static_cast(minMultiplicity), - static_cast(maxMultiplicity), - "private" /* version */, - OFTrue /* doCopyString */, - privateCreator.c_str())); - } - - entry->setGroupRangeRestriction(DcmDictRange_Unspecified); - entry->setElementRangeRestriction(DcmDictRange_Unspecified); - - { - DictionaryLocker locker; - - if (locker->findEntry(name.c_str())) - { - throw OrthancException(ErrorCode_AlreadyExistingTag, - "Cannot register two tags with the same symbolic name \"" + name + "\""); - } - - locker->addEntry(entry.release()); - } - } - - - Encoding FromDcmtkBridge::DetectEncoding(bool& hasCodeExtensions, - DcmItem& dataset, - Encoding defaultEncoding) - { - // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2 - - OFString tmp; - if (dataset.findAndGetOFStringArray(DCM_SpecificCharacterSet, tmp).good()) - { - std::vector tokens; - Toolbox::TokenizeString(tokens, std::string(tmp.c_str()), '\\'); - - hasCodeExtensions = (tokens.size() > 1); - - for (size_t i = 0; i < tokens.size(); i++) - { - std::string characterSet = Toolbox::StripSpaces(tokens[i]); - - if (!characterSet.empty()) - { - Encoding encoding; - - if (GetDicomEncoding(encoding, characterSet.c_str())) - { - // The specific character set is supported by the Orthanc core - return encoding; - } - else - { - LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet - << ", fallback to ASCII (remove all special characters)"; - return Encoding_Ascii; - } - } - } - } - else - { - hasCodeExtensions = false; - } - - // No specific character set tag: Use the default encoding - return defaultEncoding; - } - - - void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, - DcmItem& dataset, - unsigned int maxStringLength, - Encoding defaultEncoding, - const std::set& ignoreTagLength) - { - bool hasCodeExtensions; - Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); - - target.Clear(); - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - if (element && element->isLeaf()) - { - target.SetValueInternal(element->getTag().getGTag(), - element->getTag().getETag(), - ConvertLeafElement(*element, DicomToJsonFlags_Default, - maxStringLength, encoding, hasCodeExtensions, ignoreTagLength)); - } - } - } - - - DicomTag FromDcmtkBridge::Convert(const DcmTag& tag) - { - return DicomTag(tag.getGTag(), tag.getETag()); - } - - - DicomTag FromDcmtkBridge::GetTag(const DcmElement& element) - { - return DicomTag(element.getGTag(), element.getETag()); - } - - - DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding, - bool hasCodeExtensions, - const std::set& ignoreTagLength) - { - if (!element.isLeaf()) - { - // This function is only applicable to leaf elements - throw OrthancException(ErrorCode_BadParameterType); - } - - char *c = NULL; - if (element.isaString() && - element.getString(c).good()) - { - if (c == NULL) // This case corresponds to the empty string - { - return new DicomValue("", false); - } - else - { - std::string s(c); - std::string utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); - - if (maxStringLength != 0 && - utf8.size() > maxStringLength && - ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) - { - return new DicomValue; // Too long, create a NULL value - } - else - { - return new DicomValue(utf8, false); - } - } - } - - - if (element.getVR() == EVR_UN) - { - // Unknown value representation: Lookup in the dictionary. This - // is notably the case for private tags registered with the - // "Dictionary" configuration option. - DictionaryLocker locker; - - const DcmDictEntry* entry = locker->findEntry(element.getTag().getXTag(), - element.getTag().getPrivateCreator()); - if (entry != NULL && - entry->getVR().isaString()) - { - Uint8* data = NULL; - - // At (*), we do not try and convert to UTF-8, as nothing says - // the encoding of the private tag is the same as that of the - // remaining of the DICOM dataset. Only go for ASCII strings. - - if (element.getUint8Array(data) == EC_Normal && - Toolbox::IsAsciiString(data, element.getLength())) // (*) - { - if (data == NULL) - { - return new DicomValue("", false); // Empty string - } - else if (maxStringLength != 0 && - element.getLength() > maxStringLength && - ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) - { - return new DicomValue; // Too long, create a NULL value - } - else - { - std::string s(reinterpret_cast(data), element.getLength()); - return new DicomValue(s, false); - } - } - } - } - - - try - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - switch (element.getVR()) - { - - /** - * Deal with binary data (including PixelData). - **/ - - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_UN: // unknown value representation - case EVR_ox: // OB or OW depending on context - case EVR_DS: // decimal string - case EVR_IS: // integer string - case EVR_AS: // age string - case EVR_DA: // date string - case EVR_DT: // date time string - case EVR_TM: // time string - case EVR_AE: // application entity title - case EVR_CS: // code string - case EVR_SH: // short string - case EVR_LO: // long string - case EVR_ST: // short text - case EVR_LT: // long text - case EVR_UT: // unlimited text - case EVR_PN: // person name - case EVR_UI: // unique identifier - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - { - if (!(flags & DicomToJsonFlags_ConvertBinaryToNull)) - { - Uint8* data = NULL; - if (element.getUint8Array(data) == EC_Normal) - { - return new DicomValue(reinterpret_cast(data), element.getLength(), true); - } - } - - return new DicomValue; - } - - /** - * Numeric types - **/ - - case EVR_SL: // signed long - { - return ApplyDcmtkToCTypeConverter(element); - } - - case EVR_SS: // signed short - { - return ApplyDcmtkToCTypeConverter(element); - } - - case EVR_UL: // unsigned long - { - return ApplyDcmtkToCTypeConverter(element); - } - - case EVR_US: // unsigned short - { - return ApplyDcmtkToCTypeConverter(element); - } - - case EVR_FL: // float single-precision - { - return ApplyDcmtkToCTypeConverter(element); - } - - case EVR_FD: // float double-precision - { - return ApplyDcmtkToCTypeConverter(element); - } - - - /** - * Attribute tag. - **/ - - case EVR_AT: - { - DcmTagKey tag; - if (dynamic_cast(element).getTagVal(tag, 0).good()) - { - DicomTag t(tag.getGroup(), tag.getElement()); - return new DicomValue(t.Format(), false); - } - else - { - return new DicomValue; - } - } - - - /** - * Sequence types, should never occur at this point because of - * "element.isLeaf()". - **/ - - case EVR_SQ: // sequence of items - return new DicomValue; - - - /** - * Internal to DCMTK. - **/ - - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - return new DicomValue; - - - /** - * Default case. - **/ - - default: - return new DicomValue; - } - } - catch (boost::bad_lexical_cast&) - { - return new DicomValue; - } - catch (std::bad_cast&) - { - return new DicomValue; - } - } - - - static Json::Value& PrepareNode(Json::Value& parent, - DcmElement& element, - DicomToJsonFormat format) - { - assert(parent.type() == Json::objectValue); - - DicomTag tag(FromDcmtkBridge::GetTag(element)); - const std::string formattedTag = tag.Format(); - - if (format == DicomToJsonFormat_Short) - { - parent[formattedTag] = Json::nullValue; - return parent[formattedTag]; - } - - // This code gives access to the name of the private tags - std::string tagName = FromDcmtkBridge::GetTagName(element); - - switch (format) - { - case DicomToJsonFormat_Human: - parent[tagName] = Json::nullValue; - return parent[tagName]; - - case DicomToJsonFormat_Full: - { - parent[formattedTag] = Json::objectValue; - Json::Value& node = parent[formattedTag]; - - if (element.isLeaf()) - { - node["Name"] = tagName; - - if (element.getTag().getPrivateCreator() != NULL) - { - node["PrivateCreator"] = element.getTag().getPrivateCreator(); - } - - return node; - } - else - { - node["Name"] = tagName; - node["Type"] = "Sequence"; - node["Value"] = Json::nullValue; - return node["Value"]; - } - } - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - static void LeafValueToJson(Json::Value& target, - const DicomValue& value, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) - { - Json::Value* targetValue = NULL; - Json::Value* targetType = NULL; - - switch (format) - { - case DicomToJsonFormat_Short: - case DicomToJsonFormat_Human: - { - assert(target.type() == Json::nullValue); - targetValue = ⌖ - break; - } - - case DicomToJsonFormat_Full: - { - assert(target.type() == Json::objectValue); - target["Value"] = Json::nullValue; - target["Type"] = Json::nullValue; - targetType = &target["Type"]; - targetValue = &target["Value"]; - break; - } - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - assert(targetValue != NULL); - assert(targetValue->type() == Json::nullValue); - assert(targetType == NULL || targetType->type() == Json::nullValue); - - if (value.IsNull()) - { - if (targetType != NULL) - { - *targetType = "Null"; - } - } - else if (value.IsBinary()) - { - if (flags & DicomToJsonFlags_ConvertBinaryToAscii) - { - *targetValue = Toolbox::ConvertToAscii(value.GetContent()); - } - else - { - std::string s; - value.FormatDataUriScheme(s); - *targetValue = s; - } - - if (targetType != NULL) - { - *targetType = "Binary"; - } - } - else if (maxStringLength == 0 || - value.GetContent().size() <= maxStringLength) - { - *targetValue = value.GetContent(); - - if (targetType != NULL) - { - *targetType = "String"; - } - } - else - { - if (targetType != NULL) - { - *targetType = "TooLong"; - } - } - } - - - void FromDcmtkBridge::ElementToJson(Json::Value& parent, - DcmElement& element, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding, - bool hasCodeExtensions, - const std::set& ignoreTagLength) - { - if (parent.type() == Json::nullValue) - { - parent = Json::objectValue; - } - - assert(parent.type() == Json::objectValue); - Json::Value& target = PrepareNode(parent, element, format); - - if (element.isLeaf()) - { - // The "0" below lets "LeafValueToJson()" take care of "TooLong" values - std::unique_ptr v(FromDcmtkBridge::ConvertLeafElement - (element, flags, 0, encoding, hasCodeExtensions, ignoreTagLength)); - - if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end()) - { - LeafValueToJson(target, *v, format, flags, maxStringLength); - } - else - { - LeafValueToJson(target, *v, format, flags, 0); - } - } - else - { - assert(target.type() == Json::nullValue); - target = Json::arrayValue; - - // "All subclasses of DcmElement except for DcmSequenceOfItems - // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset - // etc. are not." The following dynamic_cast is thus OK. - DcmSequenceOfItems& sequence = dynamic_cast(element); - - for (unsigned long i = 0; i < sequence.card(); i++) - { - DcmItem* child = sequence.getItem(i); - Json::Value& v = target.append(Json::objectValue); - DatasetToJson(v, *child, format, flags, maxStringLength, encoding, hasCodeExtensions, ignoreTagLength); - } - } - } - - - void FromDcmtkBridge::DatasetToJson(Json::Value& parent, - DcmItem& item, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding, - bool hasCodeExtensions, - const std::set& ignoreTagLength) - { - assert(parent.type() == Json::objectValue); - - for (unsigned long i = 0; i < item.card(); i++) - { - DcmElement* element = item.getElement(i); - if (element == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - DicomTag tag(FromDcmtkBridge::Convert(element->getTag())); - - /*element->getTag().isPrivate()*/ - if (tag.IsPrivate() && - !(flags & DicomToJsonFlags_IncludePrivateTags)) - { - continue; - } - - if (!(flags & DicomToJsonFlags_IncludeUnknownTags)) - { - DictionaryLocker locker; - if (locker->findEntry(element->getTag(), element->getTag().getPrivateCreator()) == NULL) - { - continue; - } - } - - if (IsBinaryTag(element->getTag())) - { - // This is a binary tag - if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) || - (tag != DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludeBinary))) - { - continue; - } - } - - FromDcmtkBridge::ElementToJson(parent, *element, format, flags, - maxStringLength, encoding, hasCodeExtensions, ignoreTagLength); - } - } - - - void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, - DcmDataset& dataset, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding defaultEncoding, - const std::set& ignoreTagLength) - { - bool hasCodeExtensions; - Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); - - target = Json::objectValue; - DatasetToJson(target, dataset, format, flags, maxStringLength, encoding, hasCodeExtensions, ignoreTagLength); - } - - - void FromDcmtkBridge::ExtractHeaderAsJson(Json::Value& target, - DcmMetaInfo& dataset, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) - { - std::set ignoreTagLength; - target = Json::objectValue; - DatasetToJson(target, dataset, format, flags, maxStringLength, Encoding_Ascii, false, ignoreTagLength); - } - - - - static std::string GetTagNameInternal(DcmTag& tag) - { - { - // Some patches for important tags because of different DICOM - // dictionaries between DCMTK versions - DicomTag tmp(tag.getGroup(), tag.getElement()); - std::string n = tmp.GetMainTagsName(); - if (n.size() != 0) - { - return n; - } - // End of patches - } - -#if 0 - // This version explicitly calls the dictionary - const DcmDataDictionary& dict = dcmDataDict.rdlock(); - const DcmDictEntry* entry = dict.findEntry(tag, NULL); - - std::string s(DcmTag_ERROR_TagName); - if (entry != NULL) - { - s = std::string(entry->getTagName()); - } - - dcmDataDict.unlock(); - return s; -#else - const char* name = tag.getTagName(); - if (name == NULL) - { - return DcmTag_ERROR_TagName; - } - else - { - return std::string(name); - } -#endif - } - - - std::string FromDcmtkBridge::GetTagName(const DicomTag& t, - const std::string& privateCreator) - { - DcmTag tag(t.GetGroup(), t.GetElement()); - - if (!privateCreator.empty()) - { - tag.setPrivateCreator(privateCreator.c_str()); - } - - return GetTagNameInternal(tag); - } - - - std::string FromDcmtkBridge::GetTagName(const DcmElement& element) - { - // Copy the tag to ensure const-correctness of DcmElement. Note - // that the private creator information is also copied. - DcmTag tag(element.getTag()); - - return GetTagNameInternal(tag); - } - - - - DicomTag FromDcmtkBridge::ParseTag(const char* name) - { - DicomTag parsed(0, 0); - if (DicomTag::ParseHexadecimal(parsed, name)) - { - return parsed; - } - -#if 0 - const DcmDataDictionary& dict = dcmDataDict.rdlock(); - const DcmDictEntry* entry = dict.findEntry(name); - - if (entry == NULL) - { - dcmDataDict.unlock(); - throw OrthancException(ErrorCode_UnknownDicomTag); - } - else - { - DcmTagKey key = entry->getKey(); - DicomTag tag(key.getGroup(), key.getElement()); - dcmDataDict.unlock(); - return tag; - } -#else - DcmTag tag; - if (DcmTag::findTagFromName(name, tag).good()) - { - return DicomTag(tag.getGTag(), tag.getETag()); - } - else - { - LOG(INFO) << "Unknown DICOM tag: \"" << name << "\""; - throw OrthancException(ErrorCode_UnknownDicomTag); - } -#endif - } - - - bool FromDcmtkBridge::IsUnknownTag(const DicomTag& tag) - { - DcmTag tmp(tag.GetGroup(), tag.GetElement()); - return tmp.isUnknownVR(); - } - - - void FromDcmtkBridge::ToJson(Json::Value& result, - const DicomMap& values, - bool simplify) - { - if (result.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - result.clear(); - - for (DicomMap::Content::const_iterator - it = values.content_.begin(); it != values.content_.end(); ++it) - { - // TODO Inject PrivateCreator if some is available in the DicomMap? - const std::string tagName = GetTagName(it->first, ""); - - if (simplify) - { - if (it->second->IsNull()) - { - result[tagName] = Json::nullValue; - } - else - { - // TODO IsBinary - result[tagName] = it->second->GetContent(); - } - } - else - { - Json::Value value = Json::objectValue; - - value["Name"] = tagName; - - if (it->second->IsNull()) - { - value["Type"] = "Null"; - value["Value"] = Json::nullValue; - } - else - { - // TODO IsBinary - value["Type"] = "String"; - value["Value"] = it->second->GetContent(); - } - - result[it->first.Format()] = value; - } - } - } - - - std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level) - { - char uid[100]; - - switch (level) - { - case ResourceType_Patient: - // The "PatientID" field is of type LO (Long String), 64 - // Bytes Maximum. An UUID is of length 36, thus it can be used - // as a random PatientID. - return Toolbox::GenerateUuid(); - - case ResourceType_Instance: - return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); - - case ResourceType_Series: - return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); - - case ResourceType_Study: - return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - - static bool SaveToMemoryBufferInternal(std::string& buffer, - DcmFileFormat& dicom, - E_TransferSyntax xfer) - { - E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; - - // Create a memory buffer with the proper size - { - const uint32_t estimatedSize = dicom.calcElementLength(xfer, encodingType); // (*) - buffer.resize(estimatedSize); - } - - DcmOutputBufferStream ob(&buffer[0], buffer.size()); - - // Fill the memory buffer with the meta-header and the dataset - dicom.transferInit(); - OFCondition c = dicom.write(ob, xfer, encodingType, NULL, - /*opt_groupLength*/ EGL_recalcGL, - /*opt_paddingType*/ EPD_noChange, - /*padlen*/ 0, /*subPadlen*/ 0, /*instanceLength*/ 0, - EWM_updateMeta /* creates new SOP instance UID on lossy */); - dicom.transferEnd(); - - if (c.good()) - { - // The DICOM file is successfully written, truncate the target - // buffer if its size was overestimated by (*) - ob.flush(); - - size_t effectiveSize = static_cast(ob.tell()); - if (effectiveSize < buffer.size()) - { - buffer.resize(effectiveSize); - } - - return true; - } - else - { - // Error - buffer.clear(); - return false; - } - } - - - bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer, - DcmDataset& dataSet) - { - // Determine the transfer syntax which shall be used to write the - // information to the file. If not possible, switch to the Little - // Endian syntax, with explicit length. - - // http://support.dcmtk.org/docs/dcxfer_8h-source.html - - - /** - * Note that up to Orthanc 0.7.1 (inclusive), the - * "EXS_LittleEndianExplicit" was always used to save the DICOM - * dataset into memory. We now keep the original transfer syntax - * (if available). - **/ - E_TransferSyntax xfer = dataSet.getCurrentXfer(); - if (xfer == EXS_Unknown) - { - // No information about the original transfer syntax: This is - // most probably a DICOM dataset that was read from memory. - xfer = EXS_LittleEndianExplicit; - } - - // Create the meta-header information - DcmFileFormat ff(&dataSet); - ff.validateMetaInfo(xfer); - ff.removeInvalidGroups(); - - return SaveToMemoryBufferInternal(buffer, ff, xfer); - } - - - bool FromDcmtkBridge::Transcode(DcmFileFormat& dicom, - DicomTransferSyntax syntax, - const DcmRepresentationParameter* representation) - { - E_TransferSyntax xfer; - if (!LookupDcmtkTransferSyntax(xfer, syntax)) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - DicomTransferSyntax sourceSyntax; - bool known = LookupOrthancTransferSyntax(sourceSyntax, dicom); - - if (!dicom.chooseRepresentation(xfer, representation).good() || - !dicom.canWriteXfer(xfer) || - !dicom.validateMetaInfo(xfer, EWM_updateMeta).good()) - { - return false; - } - else - { - dicom.removeInvalidGroups(); - - if (known) - { - LOG(INFO) << "Transcoded an image from transfer syntax " - << GetTransferSyntaxUid(sourceSyntax) << " to " - << GetTransferSyntaxUid(syntax); - } - else - { - LOG(INFO) << "Transcoded an image from unknown transfer syntax to " - << GetTransferSyntaxUid(syntax); - } - - return true; - } - } - } - - - ValueRepresentation FromDcmtkBridge::LookupValueRepresentation(const DicomTag& tag) - { - DcmTag t(tag.GetGroup(), tag.GetElement()); - return Convert(t.getEVR()); - } - - ValueRepresentation FromDcmtkBridge::Convert(const DcmEVR vr) - { - switch (vr) - { - case EVR_AE: - return ValueRepresentation_ApplicationEntity; - - case EVR_AS: - return ValueRepresentation_AgeString; - - case EVR_AT: - return ValueRepresentation_AttributeTag; - - case EVR_CS: - return ValueRepresentation_CodeString; - - case EVR_DA: - return ValueRepresentation_Date; - - case EVR_DS: - return ValueRepresentation_DecimalString; - - case EVR_DT: - return ValueRepresentation_DateTime; - - case EVR_FL: - return ValueRepresentation_FloatingPointSingle; - - case EVR_FD: - return ValueRepresentation_FloatingPointDouble; - - case EVR_IS: - return ValueRepresentation_IntegerString; - - case EVR_LO: - return ValueRepresentation_LongString; - - case EVR_LT: - return ValueRepresentation_LongText; - - case EVR_OB: - return ValueRepresentation_OtherByte; - -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_OD: - return ValueRepresentation_OtherDouble; -#endif - - case EVR_OF: - return ValueRepresentation_OtherFloat; - -#if DCMTK_VERSION_NUMBER >= 362 - case EVR_OL: - return ValueRepresentation_OtherLong; -#endif - - case EVR_OW: - return ValueRepresentation_OtherWord; - - case EVR_PN: - return ValueRepresentation_PersonName; - - case EVR_SH: - return ValueRepresentation_ShortString; - - case EVR_SL: - return ValueRepresentation_SignedLong; - - case EVR_SQ: - return ValueRepresentation_Sequence; - - case EVR_SS: - return ValueRepresentation_SignedShort; - - case EVR_ST: - return ValueRepresentation_ShortText; - - case EVR_TM: - return ValueRepresentation_Time; - -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_UC: - return ValueRepresentation_UnlimitedCharacters; -#endif - - case EVR_UI: - return ValueRepresentation_UniqueIdentifier; - - case EVR_UL: - return ValueRepresentation_UnsignedLong; - - case EVR_UN: - return ValueRepresentation_Unknown; - -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_UR: - return ValueRepresentation_UniversalResource; -#endif - - case EVR_US: - return ValueRepresentation_UnsignedShort; - - case EVR_UT: - return ValueRepresentation_UnlimitedText; - - default: - return ValueRepresentation_NotSupported; - } - } - - - DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag, - const std::string& privateCreator) - { - if (tag.IsPrivate() && - privateCreator.empty()) - { - // This solves issue 140 (Modifying private tags with REST API - // changes VR from LO to UN) - // https://bitbucket.org/sjodogne/orthanc/issues/140 - LOG(WARNING) << "Private creator should not be empty while creating a private tag: " << tag.Format(); - } - -#if DCMTK_VERSION_NUMBER >= 362 - DcmTag key(tag.GetGroup(), tag.GetElement()); - if (tag.IsPrivate()) - { - return DcmItem::newDicomElement(key, privateCreator.c_str()); - } - else - { - return DcmItem::newDicomElement(key, NULL); - } - -#else - DcmTag key(tag.GetGroup(), tag.GetElement()); - if (tag.IsPrivate()) - { - // https://forum.dcmtk.org/viewtopic.php?t=4527 - LOG(WARNING) << "You are using DCMTK <= 3.6.1: All the private tags " - "are considered as having a binary value representation"; - key.setPrivateCreator(privateCreator.c_str()); - return new DcmOtherByteOtherWord(key); - } - else - { - return newDicomElement(key); - } -#endif - } - - - - void FromDcmtkBridge::FillElementWithString(DcmElement& element, - const std::string& utf8Value, - bool decodeDataUriScheme, - Encoding dicomEncoding) - { - std::string binary; - const std::string* decoded = &utf8Value; - - if (decodeDataUriScheme && - boost::starts_with(utf8Value, URI_SCHEME_PREFIX_BINARY)) - { - std::string mime; - if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - decoded = &binary; - } - else if (dicomEncoding != Encoding_Utf8) - { - binary = Toolbox::ConvertFromUtf8(utf8Value, dicomEncoding); - decoded = &binary; - } - - if (IsBinaryTag(element.getTag())) - { - bool ok; - - switch (element.getTag().getEVR()) - { - case EVR_OW: - if (decoded->size() % sizeof(Uint16) != 0) - { - LOG(ERROR) << "A tag with OW VR must have an even number of bytes"; - ok = false; - } - else - { - ok = element.putUint16Array((const Uint16*) decoded->c_str(), decoded->size() / sizeof(Uint16)).good(); - } - - break; - - default: - ok = element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good(); - break; - } - - if (ok) - { - return; - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - } - - bool ok = false; - - try - { - switch (element.getTag().getEVR()) - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - - /** - * TODO. - **/ - - case EVR_OB: // other byte - case EVR_OW: // other word - case EVR_AT: // attribute tag - throw OrthancException(ErrorCode_NotImplemented); - - case EVR_UN: // unknown value representation - throw OrthancException(ErrorCode_ParameterOutOfRange); - - - /** - * String types. - **/ - - case EVR_DS: // decimal string - case EVR_IS: // integer string - case EVR_AS: // age string - case EVR_DA: // date string - case EVR_DT: // date time string - case EVR_TM: // time string - case EVR_AE: // application entity title - case EVR_CS: // code string - case EVR_SH: // short string - case EVR_LO: // long string - case EVR_ST: // short text - case EVR_LT: // long text - case EVR_UT: // unlimited text - case EVR_PN: // person name - case EVR_UI: // unique identifier -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_UC: // unlimited characters - case EVR_UR: // URI/URL -#endif - { - ok = element.putString(decoded->c_str()).good(); - break; - } - - - /** - * Numerical types - **/ - - case EVR_SL: // signed long - { - ok = element.putSint32(boost::lexical_cast(*decoded)).good(); - break; - } - - case EVR_SS: // signed short - { - ok = element.putSint16(boost::lexical_cast(*decoded)).good(); - break; - } - - case EVR_UL: // unsigned long -#if DCMTK_VERSION_NUMBER >= 362 - case EVR_OL: // other long (requires byte-swapping) -#endif - { - ok = element.putUint32(boost::lexical_cast(*decoded)).good(); - break; - } - - case EVR_US: // unsigned short - { - ok = element.putUint16(boost::lexical_cast(*decoded)).good(); - break; - } - - case EVR_FL: // float single-precision - case EVR_OF: // other float (requires byte swapping) - { - ok = element.putFloat32(boost::lexical_cast(*decoded)).good(); - break; - } - - case EVR_FD: // float double-precision -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_OD: // other double (requires byte-swapping) -#endif - { - ok = element.putFloat64(boost::lexical_cast(*decoded)).good(); - break; - } - - - /** - * Sequence types, should never occur at this point. - **/ - - case EVR_SQ: // sequence of items - { - ok = false; - break; - } - - - /** - * Internal to DCMTK. - **/ - - case EVR_ox: // OB or OW depending on context - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - default: - break; - } - } - catch (boost::bad_lexical_cast&) - { - ok = false; - } - - if (!ok) - { - DicomTag tag(element.getTag().getGroup(), element.getTag().getElement()); - throw OrthancException(ErrorCode_BadFileFormat, - "While creating a DICOM instance, tag (" + tag.Format() + - ") has out-of-range value: \"" + (*decoded) + "\""); - } - } - - - DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, - const Json::Value& value, - bool decodeDataUriScheme, - Encoding dicomEncoding, - const std::string& privateCreator) - { - std::unique_ptr element; - - switch (value.type()) - { - case Json::stringValue: - element.reset(CreateElementForTag(tag, privateCreator)); - FillElementWithString(*element, value.asString(), decodeDataUriScheme, dicomEncoding); - break; - - case Json::nullValue: - element.reset(CreateElementForTag(tag, privateCreator)); - FillElementWithString(*element, "", decodeDataUriScheme, dicomEncoding); - break; - - case Json::arrayValue: - { - const char* p = NULL; - if (tag.IsPrivate() && - !privateCreator.empty()) - { - p = privateCreator.c_str(); - } - - DcmTag key(tag.GetGroup(), tag.GetElement(), p); - if (key.getEVR() != EVR_SQ) - { - throw OrthancException(ErrorCode_BadParameterType, - "Bad Parameter type for tag " + tag.Format()); - } - - DcmSequenceOfItems* sequence = new DcmSequenceOfItems(key); - element.reset(sequence); - - for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) - { - std::unique_ptr item(new DcmItem); - - switch (value[i].type()) - { - case Json::objectValue: - { - Json::Value::Members members = value[i].getMemberNames(); - for (Json::Value::ArrayIndex j = 0; j < members.size(); j++) - { - item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding, privateCreator)); - } - break; - } - - case Json::arrayValue: - { - // Lua cannot disambiguate between an empty dictionary - // and an empty array - if (value[i].size() != 0) - { - throw OrthancException(ErrorCode_BadParameterType); - } - break; - } - - default: - throw OrthancException(ErrorCode_BadParameterType); - } - - sequence->append(item.release()); - } - - break; - } - - default: - throw OrthancException(ErrorCode_BadParameterType, "Bad Parameter type for tag " + tag.Format()); - } - - return element.release(); - } - - - DcmPixelSequence* FromDcmtkBridge::GetPixelSequence(DcmDataset& dataset) - { - DcmElement *element = NULL; - if (!dataset.findAndGetElement(DCM_PixelData, element).good()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - DcmPixelData& pixelData = dynamic_cast(*element); - - E_TransferSyntax repType; - const DcmRepresentationParameter *repParam = NULL; - pixelData.getCurrentRepresentationKey(repType, repParam); - - DcmPixelSequence* pixelSequence = NULL; - if (!pixelData.getEncapsulatedRepresentation(repType, repParam, pixelSequence).good()) - { - return NULL; - } - else - { - return pixelSequence; - } - } - - - Encoding FromDcmtkBridge::ExtractEncoding(const Json::Value& json, - Encoding defaultEncoding) - { - if (json.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - Encoding encoding = defaultEncoding; - - const Json::Value::Members tags = json.getMemberNames(); - - // Look for SpecificCharacterSet (0008,0005) in the JSON file - for (size_t i = 0; i < tags.size(); i++) - { - DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); - if (tag == DICOM_TAG_SPECIFIC_CHARACTER_SET) - { - const Json::Value& value = json[tags[i]]; - if (value.type() != Json::stringValue || - (value.asString().length() != 0 && - !GetDicomEncoding(encoding, value.asCString()))) - { - throw OrthancException(ErrorCode_BadRequest, - "Unknown encoding while creating DICOM from JSON: " + - value.toStyledString()); - } - - if (value.asString().length() == 0) - { - return defaultEncoding; - } - } - } - - return encoding; - } - - - static void SetString(DcmDataset& target, - const DcmTag& tag, - const std::string& value) - { - if (!target.putAndInsertString(tag, value.c_str()).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json, // Encoded using UTF-8 - bool generateIdentifiers, - bool decodeDataUriScheme, - Encoding defaultEncoding, - const std::string& privateCreator) - { - std::unique_ptr result(new DcmDataset); - Encoding encoding = ExtractEncoding(json, defaultEncoding); - - SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding)); - - const Json::Value::Members tags = json.getMemberNames(); - - bool hasPatientId = false; - bool hasStudyInstanceUid = false; - bool hasSeriesInstanceUid = false; - bool hasSopInstanceUid = false; - - for (size_t i = 0; i < tags.size(); i++) - { - DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); - const Json::Value& value = json[tags[i]]; - - if (tag == DICOM_TAG_PATIENT_ID) - { - hasPatientId = true; - } - else if (tag == DICOM_TAG_STUDY_INSTANCE_UID) - { - hasStudyInstanceUid = true; - } - else if (tag == DICOM_TAG_SERIES_INSTANCE_UID) - { - hasSeriesInstanceUid = true; - } - else if (tag == DICOM_TAG_SOP_INSTANCE_UID) - { - hasSopInstanceUid = true; - } - - if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) - { - std::unique_ptr element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); - const DcmTagKey& tag = element->getTag(); - - result->findAndDeleteElement(tag); - - DcmElement* tmp = element.release(); - if (!result->insert(tmp, false, false).good()) - { - delete tmp; - throw OrthancException(ErrorCode_InternalError); - } - } - } - - if (!hasPatientId && - generateIdentifiers) - { - SetString(*result, DCM_PatientID, GenerateUniqueIdentifier(ResourceType_Patient)); - } - - if (!hasStudyInstanceUid && - generateIdentifiers) - { - SetString(*result, DCM_StudyInstanceUID, GenerateUniqueIdentifier(ResourceType_Study)); - } - - if (!hasSeriesInstanceUid && - generateIdentifiers) - { - SetString(*result, DCM_SeriesInstanceUID, GenerateUniqueIdentifier(ResourceType_Series)); - } - - if (!hasSopInstanceUid && - generateIdentifiers) - { - SetString(*result, DCM_SOPInstanceUID, GenerateUniqueIdentifier(ResourceType_Instance)); - } - - return result.release(); - } - - - DcmFileFormat* FromDcmtkBridge::LoadFromMemoryBuffer(const void* buffer, - size_t size) - { - DcmInputBufferStream is; - if (size > 0) - { - is.setBuffer(buffer, size); - } - is.setEos(); - - std::unique_ptr result(new DcmFileFormat); - - result->transferInit(); - - /** - * New in Orthanc 1.6.0: The "size" is given as an argument to the - * "read()" method. This can avoid huge memory consumption if - * parsing an invalid DICOM file, which can notably been observed - * by executing the integration test "test_upload_compressed" on - * valgrind running Orthanc. - **/ - if (!result->read(is, EXS_Unknown, EGL_noChange, size).good()) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot parse an invalid DICOM file (size: " + - boost::lexical_cast(size) + " bytes)"); - } - - result->loadAllDataIntoMemory(); - result->transferEnd(); - - return result.release(); - } - - - void FromDcmtkBridge::FromJson(DicomMap& target, - const Json::Value& source) - { - if (source.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - target.Clear(); - - Json::Value::Members members = source.getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& value = source[members[i]]; - - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - target.SetValue(ParseTag(members[i]), value.asString(), false); - } - } - - - void FromDcmtkBridge::ChangeStringEncoding(DcmItem& dataset, - Encoding source, - bool hasSourceCodeExtensions, - Encoding target) - { - // Recursive exploration of a dataset to change the encoding of - // each string-like element - - if (source == target) - { - return; - } - - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - if (element) - { - if (element->isLeaf()) - { - char *c = NULL; - if (element->isaString() && - element->getString(c).good() && - c != NULL) - { - std::string a = Toolbox::ConvertToUtf8(c, source, hasSourceCodeExtensions); - std::string b = Toolbox::ConvertFromUtf8(a, target); - element->putString(b.c_str()); - } - } - else - { - // "All subclasses of DcmElement except for DcmSequenceOfItems - // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset - // etc. are not." The following dynamic_cast is thus OK. - DcmSequenceOfItems& sequence = dynamic_cast(*element); - - for (unsigned long j = 0; j < sequence.card(); j++) - { - ChangeStringEncoding(*sequence.getItem(j), source, hasSourceCodeExtensions, target); - } - } - } - } - } - - -#if ORTHANC_ENABLE_LUA == 1 - void FromDcmtkBridge::ExecuteToDicom(DicomMap& target, - LuaFunctionCall& call) - { - Json::Value output; - call.ExecuteToJson(output, true /* keep strings */); - - target.Clear(); - - if (output.type() == Json::arrayValue && - output.size() == 0) - { - // This case happens for empty tables - return; - } - - if (output.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_LuaBadOutput, - "Lua: The script must return a table"); - } - - Json::Value::Members members = output.getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - if (output[members[i]].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_LuaBadOutput, - "Lua: The script must return a table " - "mapping names of DICOM tags to strings"); - } - - DicomTag tag(ParseTag(members[i])); - target.SetValue(tag, output[members[i]].asString(), false); - } - } -#endif - - - void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, - DcmItem& dataset, - const std::set& ignoreTagLength) - { - ExtractDicomSummary(target, dataset, - ORTHANC_MAXIMUM_TAG_LENGTH, - GetDefaultDicomEncoding(), ignoreTagLength); - } - - - void FromDcmtkBridge::ExtractDicomAsJson(Json::Value& target, - DcmDataset& dataset, - const std::set& ignoreTagLength) - { - ExtractDicomAsJson(target, dataset, - DicomToJsonFormat_Full, - DicomToJsonFlags_Default, - ORTHANC_MAXIMUM_TAG_LENGTH, - GetDefaultDicomEncoding(), - ignoreTagLength); - } - - - void FromDcmtkBridge::InitializeCodecs() - { -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - LOG(INFO) << "Registering JPEG Lossless codecs in DCMTK"; - DJLSDecoderRegistration::registerCodecs(); -# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - DJLSEncoderRegistration::registerCodecs(); -# endif -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - LOG(INFO) << "Registering JPEG codecs in DCMTK"; - DJDecoderRegistration::registerCodecs(); -# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - DJEncoderRegistration::registerCodecs(); -# endif -#endif - - LOG(INFO) << "Registering RLE codecs in DCMTK"; - DcmRLEDecoderRegistration::registerCodecs(); -#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - DcmRLEEncoderRegistration::registerCodecs(); -#endif - } - - - void FromDcmtkBridge::FinalizeCodecs() - { -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - // Unregister JPEG-LS codecs - DJLSDecoderRegistration::cleanup(); -# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - DJLSEncoderRegistration::cleanup(); -# endif -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - // Unregister JPEG codecs - DJDecoderRegistration::cleanup(); -# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - DJEncoderRegistration::cleanup(); -# endif -#endif - - DcmRLEDecoderRegistration::cleanup(); -#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1 - DcmRLEEncoderRegistration::cleanup(); -#endif - } - - - - // Forward declaration - static void ApplyVisitorToElement(DcmElement& element, - ITagVisitor& visitor, - const std::vector& parentTags, - const std::vector& parentIndexes, - Encoding encoding, - bool hasCodeExtensions); - - static void ApplyVisitorToDataset(DcmItem& dataset, - ITagVisitor& visitor, - const std::vector& parentTags, - const std::vector& parentIndexes, - Encoding encoding, - bool hasCodeExtensions) - { - assert(parentTags.size() == parentIndexes.size()); - - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - if (element == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - ApplyVisitorToElement(*element, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions); - } - } - } - - - static void ApplyVisitorToLeaf(DcmElement& element, - ITagVisitor& visitor, - const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - Encoding encoding, - bool hasCodeExtensions) - { - // TODO - Merge this function, that is more recent, with ConvertLeafElement() - - assert(element.isLeaf()); - - DcmEVR evr = element.getTag().getEVR(); - - - /** - * Fix the EVR for types internal to DCMTK - **/ - - if (evr == EVR_ox) // OB or OW depending on context - { - evr = EVR_OB; - } - - if (evr == EVR_UNKNOWN || // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - evr == EVR_UNKNOWN2B) // used internally for elements with unknown VR with 2-byte length field in explicit VR - { - evr = EVR_UN; - } - - const ValueRepresentation vr = FromDcmtkBridge::Convert(evr); - - - /** - * Deal with binary data (including PixelData). - **/ - - if (evr == EVR_OB || // other byte - evr == EVR_OW || // other word - evr == EVR_UN) // unknown value representation - { - Uint16* data16 = NULL; - Uint8* data = NULL; - - if (evr == EVR_OW && - element.getUint16Array(data16) == EC_Normal) - { - visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data16, element.getLength()); - } - else if (evr != EVR_OW && - element.getUint8Array(data) == EC_Normal) - { - visitor.VisitBinary(parentTags, parentIndexes, tag, vr, data, element.getLength()); - } - else - { - visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); - } - - return; // We're done - } - - - /** - * Deal with plain strings (and convert them to UTF-8) - **/ - - char *c = NULL; - if (element.isaString() && - element.getString(c).good()) - { - std::string utf8; - - if (c != NULL) // This case corresponds to the empty string - { - if (element.getTag() == DCM_SpecificCharacterSet) - { - utf8.assign(c); - } - else - { - std::string s(c); - utf8 = Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions); - } - } - - std::string newValue; - ITagVisitor::Action action = visitor.VisitString - (newValue, parentTags, parentIndexes, tag, vr, utf8); - - switch (action) - { - case ITagVisitor::Action_None: - break; - - case ITagVisitor::Action_Replace: - { - std::string s = Toolbox::ConvertFromUtf8(newValue, encoding); - if (element.putString(s.c_str()) != EC_Normal) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot replace value of tag: " + tag.Format()); - } - - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - - return; // We're done - } - - - try - { - // http://support.dcmtk.org/docs/dcvr_8h-source.html - switch (evr) - { - - /** - * Plain string values. - **/ - - case EVR_DS: // decimal string - case EVR_IS: // integer string - case EVR_AS: // age string - case EVR_DA: // date string - case EVR_DT: // date time string - case EVR_TM: // time string - case EVR_AE: // application entity title - case EVR_CS: // code string - case EVR_SH: // short string - case EVR_LO: // long string - case EVR_ST: // short text - case EVR_LT: // long text - case EVR_UT: // unlimited text - case EVR_PN: // person name - case EVR_UI: // unique identifier - { - Uint8* data = NULL; - - if (element.getUint8Array(data) == EC_Normal) - { - const Uint32 length = element.getLength(); - Uint32 l = 0; - while (l < length && - data[l] != 0) - { - l++; - } - - if (l == length) - { - // Not a null-terminated plain string - visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); - } - else - { - std::string ignored; - std::string s(reinterpret_cast(data), l); - ITagVisitor::Action action = visitor.VisitString - (ignored, parentTags, parentIndexes, tag, vr, - Toolbox::ConvertToUtf8(s, encoding, hasCodeExtensions)); - - if (action != ITagVisitor::Action_None) - { - LOG(WARNING) << "Cannot replace this string tag: " - << FromDcmtkBridge::GetTagName(element) - << " (" << tag.Format() << ")"; - } - } - } - else - { - visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); - } - - return; - } - - /** - * Numeric types - **/ - - case EVR_SL: // signed long - { - DcmSignedLong& content = dynamic_cast(element); - - std::vector values; - values.reserve(content.getVM()); - - for (unsigned long i = 0; i < content.getVM(); i++) - { - Sint32 f; - if (content.getSint32(f, i).good()) - { - values.push_back(f); - } - } - - visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values); - break; - } - - case EVR_SS: // signed short - { - DcmSignedShort& content = dynamic_cast(element); - - std::vector values; - values.reserve(content.getVM()); - - for (unsigned long i = 0; i < content.getVM(); i++) - { - Sint16 f; - if (content.getSint16(f, i).good()) - { - values.push_back(f); - } - } - - visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values); - break; - } - - case EVR_UL: // unsigned long -#if DCMTK_VERSION_NUMBER >= 362 - case EVR_OL: -#endif - { - DcmUnsignedLong& content = dynamic_cast(element); - - std::vector values; - values.reserve(content.getVM()); - - for (unsigned long i = 0; i < content.getVM(); i++) - { - Uint32 f; - if (content.getUint32(f, i).good()) - { - values.push_back(f); - } - } - - visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values); - break; - } - - case EVR_US: // unsigned short - { - DcmUnsignedShort& content = dynamic_cast(element); - - std::vector values; - values.reserve(content.getVM()); - - for (unsigned long i = 0; i < content.getVM(); i++) - { - Uint16 f; - if (content.getUint16(f, i).good()) - { - values.push_back(f); - } - } - - visitor.VisitIntegers(parentTags, parentIndexes, tag, vr, values); - break; - } - - case EVR_FL: // float single-precision - case EVR_OF: - { - DcmFloatingPointSingle& content = dynamic_cast(element); - - std::vector values; - values.reserve(content.getVM()); - - for (unsigned long i = 0; i < content.getVM(); i++) - { - Float32 f; - if (content.getFloat32(f, i).good()) - { - values.push_back(f); - } - } - - visitor.VisitDoubles(parentTags, parentIndexes, tag, vr, values); - break; - } - - case EVR_FD: // float double-precision -#if DCMTK_VERSION_NUMBER >= 361 - case EVR_OD: -#endif - { - DcmFloatingPointDouble& content = dynamic_cast(element); - - std::vector values; - values.reserve(content.getVM()); - - for (unsigned long i = 0; i < content.getVM(); i++) - { - Float64 f; - if (content.getFloat64(f, i).good()) - { - values.push_back(f); - } - } - - visitor.VisitDoubles(parentTags, parentIndexes, tag, vr, values); - break; - } - - - /** - * Attribute tag. - **/ - - case EVR_AT: - { - DcmAttributeTag& content = dynamic_cast(element); - - std::vector values; - values.reserve(content.getVM()); - - for (unsigned long i = 0; i < content.getVM(); i++) - { - DcmTagKey f; - if (content.getTagVal(f, i).good()) - { - DicomTag t(f.getGroup(), f.getElement()); - values.push_back(t); - } - } - - assert(vr == ValueRepresentation_AttributeTag); - visitor.VisitAttributes(parentTags, parentIndexes, tag, values); - break; - } - - - /** - * Sequence types, should never occur at this point because of - * "element.isLeaf()". - **/ - - case EVR_SQ: // sequence of items - { - return; - } - - - /** - * Internal to DCMTK. - **/ - - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - { - visitor.VisitNotSupported(parentTags, parentIndexes, tag, vr); - return; - } - - - /** - * Default case. - **/ - - default: - return; - } - } - catch (boost::bad_lexical_cast&) - { - return; - } - catch (std::bad_cast&) - { - return; - } - } - - - static void ApplyVisitorToElement(DcmElement& element, - ITagVisitor& visitor, - const std::vector& parentTags, - const std::vector& parentIndexes, - Encoding encoding, - bool hasCodeExtensions) - { - assert(parentTags.size() == parentIndexes.size()); - - DicomTag tag(FromDcmtkBridge::Convert(element.getTag())); - - if (element.isLeaf()) - { - ApplyVisitorToLeaf(element, visitor, parentTags, parentIndexes, tag, encoding, hasCodeExtensions); - } - else - { - // "All subclasses of DcmElement except for DcmSequenceOfItems - // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset - // etc. are not." The following dynamic_cast is thus OK. - DcmSequenceOfItems& sequence = dynamic_cast(element); - - if (sequence.card() == 0) - { - visitor.VisitEmptySequence(parentTags, parentIndexes, tag); - } - else - { - std::vector tags = parentTags; - std::vector indexes = parentIndexes; - tags.push_back(tag); - indexes.push_back(0); - - for (unsigned long i = 0; i < sequence.card(); i++) - { - indexes.back() = static_cast(i); - DcmItem* child = sequence.getItem(i); - ApplyVisitorToDataset(*child, visitor, tags, indexes, encoding, hasCodeExtensions); - } - } - } - } - - - void FromDcmtkBridge::Apply(DcmItem& dataset, - ITagVisitor& visitor, - Encoding defaultEncoding) - { - std::vector parentTags; - std::vector parentIndexes; - bool hasCodeExtensions; - Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); - ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions); - } - - - - bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target, - DcmFileFormat& dicom) - { - if (dicom.getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - DcmDataset& dataset = *dicom.getDataset(); - - E_TransferSyntax xfer = dataset.getCurrentXfer(); - if (xfer == EXS_Unknown) - { - dataset.updateOriginalXfer(); - xfer = dataset.getOriginalXfer(); - if (xfer == EXS_Unknown) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot determine the transfer syntax of the DICOM instance"); - } - } - - return FromDcmtkBridge::LookupOrthancTransferSyntax(target, xfer); - } -} - - -#include "./FromDcmtkBridge_TransferSyntaxes.impl.h" diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/FromDcmtkBridge.h --- a/Core/DicomParsing/FromDcmtkBridge.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,284 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ITagVisitor.h" -#include "../DicomFormat/DicomElement.h" -#include "../DicomFormat/DicomMap.h" - -#include -#include -#include -#include -#include - -#if !defined(ORTHANC_ENABLE_LUA) -# error The macro ORTHANC_ENABLE_LUA must be defined -#endif - -#if ORTHANC_ENABLE_DCMTK != 1 -# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 -#endif - -#if ORTHANC_BUILD_UNIT_TESTS == 1 -# include -#endif - -#if ORTHANC_ENABLE_LUA == 1 -# include "../Lua/LuaFunctionCall.h" -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) -# error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) -# error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined -#endif - - -namespace Orthanc -{ - class ORTHANC_PUBLIC FromDcmtkBridge : public boost::noncopyable - { -#if ORTHANC_BUILD_UNIT_TESTS == 1 - FRIEND_TEST(FromDcmtkBridge, FromJson); -#endif - - friend class ParsedDicomFile; - - private: - FromDcmtkBridge(); // Pure static class - - static void ExtractDicomSummary(DicomMap& target, - DcmItem& dataset, - unsigned int maxStringLength, - Encoding defaultEncoding, - const std::set& ignoreTagLength); - - static void DatasetToJson(Json::Value& parent, - DcmItem& item, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding, - bool hasCodeExtensions, - const std::set& ignoreTagLength); - - static void ElementToJson(Json::Value& parent, - DcmElement& element, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding dicomEncoding, - bool hasCodeExtensions, - const std::set& ignoreTagLength); - - static void ExtractDicomAsJson(Json::Value& target, - DcmDataset& dataset, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding defaultEncoding, - const std::set& ignoreTagLength); - - static void ChangeStringEncoding(DcmItem& dataset, - Encoding source, - bool hasSourceCodeExtensions, - Encoding target); - - public: - static void InitializeDictionary(bool loadPrivateDictionary); - - static void RegisterDictionaryTag(const DicomTag& tag, - ValueRepresentation vr, - const std::string& name, - unsigned int minMultiplicity, - unsigned int maxMultiplicity, - const std::string& privateCreator); - - static Encoding DetectEncoding(bool& hasCodeExtensions, - DcmItem& dataset, - Encoding defaultEncoding); - - static Encoding DetectEncoding(DcmItem& dataset, - Encoding defaultEncoding) - { - // Compatibility wrapper for Orthanc <= 1.5.4 - bool hasCodeExtensions; // ignored - return DetectEncoding(hasCodeExtensions, dataset, defaultEncoding); - } - - static DicomTag Convert(const DcmTag& tag); - - static DicomTag GetTag(const DcmElement& element); - - static bool IsUnknownTag(const DicomTag& tag); - - static DicomValue* ConvertLeafElement(DcmElement& element, - DicomToJsonFlags flags, - unsigned int maxStringLength, - Encoding encoding, - bool hasCodeExtensions, - const std::set& ignoreTagLength); - - static void ExtractHeaderAsJson(Json::Value& target, - DcmMetaInfo& header, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength); - - static std::string GetTagName(const DicomTag& tag, - const std::string& privateCreator); - - static std::string GetTagName(const DcmElement& element); - - static std::string GetTagName(const DicomElement& element) - { - return GetTagName(element.GetTag(), ""); - } - - static DicomTag ParseTag(const char* name); - - static DicomTag ParseTag(const std::string& name) - { - return ParseTag(name.c_str()); - } - - static bool HasTag(const DicomMap& fields, - const std::string& tagName) - { - return fields.HasTag(ParseTag(tagName)); - } - - static const DicomValue& GetValue(const DicomMap& fields, - const std::string& tagName) - { - return fields.GetValue(ParseTag(tagName)); - } - - static void SetValue(DicomMap& target, - const std::string& tagName, - DicomValue* value) - { - const DicomTag tag = ParseTag(tagName); - target.SetValueInternal(tag.GetGroup(), tag.GetElement(), value); - } - - static void ToJson(Json::Value& result, - const DicomMap& values, - bool simplify); - - static std::string GenerateUniqueIdentifier(ResourceType level); - - static bool SaveToMemoryBuffer(std::string& buffer, - DcmDataset& dataSet); - - static bool Transcode(DcmFileFormat& dicom, - DicomTransferSyntax syntax, - const DcmRepresentationParameter* representation); - - static ValueRepresentation Convert(DcmEVR vr); - - static ValueRepresentation LookupValueRepresentation(const DicomTag& tag); - - static DcmElement* CreateElementForTag(const DicomTag& tag, - const std::string& privateCreator); - - static void FillElementWithString(DcmElement& element, - const std::string& utf8alue, // Encoded using UTF-8 - bool decodeDataUriScheme, - Encoding dicomEncoding); - - static DcmElement* FromJson(const DicomTag& tag, - const Json::Value& element, // Encoded using UTF-8 - bool decodeDataUriScheme, - Encoding dicomEncoding, - const std::string& privateCreator); - - static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset); - - static Encoding ExtractEncoding(const Json::Value& json, - Encoding defaultEncoding); - - static DcmDataset* FromJson(const Json::Value& json, // Encoded using UTF-8 - bool generateIdentifiers, - bool decodeDataUriScheme, - Encoding defaultEncoding, - const std::string& privateCreator); - - static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer, - size_t size); - - static void FromJson(DicomMap& values, - const Json::Value& result); - -#if ORTHANC_ENABLE_LUA == 1 - static void ExecuteToDicom(DicomMap& target, - LuaFunctionCall& call); -#endif - - static void ExtractDicomSummary(DicomMap& target, - DcmItem& dataset, - const std::set& ignoreTagLength); - - static void ExtractDicomSummary(DicomMap& target, - DcmItem& dataset) - { - std::set none; - ExtractDicomSummary(target, dataset, none); - } - - static void ExtractDicomAsJson(Json::Value& target, - DcmDataset& dataset, - const std::set& ignoreTagLength); - - static void InitializeCodecs(); - - static void FinalizeCodecs(); - - static void Apply(DcmItem& dataset, - ITagVisitor& visitor, - Encoding defaultEncoding); - - static bool LookupDcmtkTransferSyntax(E_TransferSyntax& target, - DicomTransferSyntax source); - - static bool LookupOrthancTransferSyntax(DicomTransferSyntax& target, - E_TransferSyntax source); - - static bool LookupOrthancTransferSyntax(DicomTransferSyntax& target, - DcmFileFormat& dicom); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/FromDcmtkBridge_TransferSyntaxes.impl.h --- a/Core/DicomParsing/FromDcmtkBridge_TransferSyntaxes.impl.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,549 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - -// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py" - -namespace Orthanc -{ - bool FromDcmtkBridge::LookupDcmtkTransferSyntax(E_TransferSyntax& target, - DicomTransferSyntax source) - { - switch (source) - { - case DicomTransferSyntax_LittleEndianImplicit: - target = EXS_LittleEndianImplicit; - return true; - - case DicomTransferSyntax_LittleEndianExplicit: - target = EXS_LittleEndianExplicit; - return true; - - case DicomTransferSyntax_DeflatedLittleEndianExplicit: - target = EXS_DeflatedLittleEndianExplicit; - return true; - - case DicomTransferSyntax_BigEndianExplicit: - target = EXS_BigEndianExplicit; - return true; - - case DicomTransferSyntax_JPEGProcess1: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess1TransferSyntax; -# else - target = EXS_JPEGProcess1; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess2_4: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess2_4TransferSyntax; -# else - target = EXS_JPEGProcess2_4; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess3_5: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess3_5TransferSyntax; -# else - target = EXS_JPEGProcess3_5; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess6_8: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess6_8TransferSyntax; -# else - target = EXS_JPEGProcess6_8; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess7_9: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess7_9TransferSyntax; -# else - target = EXS_JPEGProcess7_9; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess10_12: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess10_12TransferSyntax; -# else - target = EXS_JPEGProcess10_12; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess11_13: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess11_13TransferSyntax; -# else - target = EXS_JPEGProcess11_13; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess14: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess14TransferSyntax; -# else - target = EXS_JPEGProcess14; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess15: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess15TransferSyntax; -# else - target = EXS_JPEGProcess15; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess16_18: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess16_18TransferSyntax; -# else - target = EXS_JPEGProcess16_18; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess17_19: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess17_19TransferSyntax; -# else - target = EXS_JPEGProcess17_19; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess20_22: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess20_22TransferSyntax; -# else - target = EXS_JPEGProcess20_22; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess21_23: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess21_23TransferSyntax; -# else - target = EXS_JPEGProcess21_23; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess24_26: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess24_26TransferSyntax; -# else - target = EXS_JPEGProcess24_26; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess25_27: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess25_27TransferSyntax; -# else - target = EXS_JPEGProcess25_27; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess28: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess28TransferSyntax; -# else - target = EXS_JPEGProcess28; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess29: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess29TransferSyntax; -# else - target = EXS_JPEGProcess29; -# endif - return true; - - case DicomTransferSyntax_JPEGProcess14SV1: -# if DCMTK_VERSION_NUMBER <= 360 - target = EXS_JPEGProcess14SV1TransferSyntax; -# else - target = EXS_JPEGProcess14SV1; -# endif - return true; - - case DicomTransferSyntax_JPEGLSLossless: - target = EXS_JPEGLSLossless; - return true; - - case DicomTransferSyntax_JPEGLSLossy: - target = EXS_JPEGLSLossy; - return true; - - case DicomTransferSyntax_JPEG2000LosslessOnly: - target = EXS_JPEG2000LosslessOnly; - return true; - - case DicomTransferSyntax_JPEG2000: - target = EXS_JPEG2000; - return true; - - case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly: - target = EXS_JPEG2000MulticomponentLosslessOnly; - return true; - - case DicomTransferSyntax_JPEG2000Multicomponent: - target = EXS_JPEG2000Multicomponent; - return true; - - case DicomTransferSyntax_JPIPReferenced: - target = EXS_JPIPReferenced; - return true; - - case DicomTransferSyntax_JPIPReferencedDeflate: - target = EXS_JPIPReferencedDeflate; - return true; - - case DicomTransferSyntax_MPEG2MainProfileAtMainLevel: - target = EXS_MPEG2MainProfileAtMainLevel; - return true; - - case DicomTransferSyntax_MPEG2MainProfileAtHighLevel: - target = EXS_MPEG2MainProfileAtHighLevel; - return true; - -#if DCMTK_VERSION_NUMBER >= 361 - case DicomTransferSyntax_MPEG4HighProfileLevel4_1: - target = EXS_MPEG4HighProfileLevel4_1; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1: - target = EXS_MPEG4BDcompatibleHighProfileLevel4_1; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo: - target = EXS_MPEG4HighProfileLevel4_2_For2DVideo; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo: - target = EXS_MPEG4HighProfileLevel4_2_For3DVideo; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2: - target = EXS_MPEG4StereoHighProfileLevel4_2; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 362 - case DicomTransferSyntax_HEVCMainProfileLevel5_1: - target = EXS_HEVCMainProfileLevel5_1; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 362 - case DicomTransferSyntax_HEVCMain10ProfileLevel5_1: - target = EXS_HEVCMain10ProfileLevel5_1; - return true; -#endif - - case DicomTransferSyntax_RLELossless: - target = EXS_RLELossless; - return true; - - default: - return false; - } - } - - - bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target, - E_TransferSyntax source) - { - switch (source) - { - case EXS_LittleEndianImplicit: - target = DicomTransferSyntax_LittleEndianImplicit; - return true; - - case EXS_LittleEndianExplicit: - target = DicomTransferSyntax_LittleEndianExplicit; - return true; - - case EXS_DeflatedLittleEndianExplicit: - target = DicomTransferSyntax_DeflatedLittleEndianExplicit; - return true; - - case EXS_BigEndianExplicit: - target = DicomTransferSyntax_BigEndianExplicit; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess1TransferSyntax: -# else - case EXS_JPEGProcess1: -# endif - target = DicomTransferSyntax_JPEGProcess1; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess2_4TransferSyntax: -# else - case EXS_JPEGProcess2_4: -# endif - target = DicomTransferSyntax_JPEGProcess2_4; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess3_5TransferSyntax: -# else - case EXS_JPEGProcess3_5: -# endif - target = DicomTransferSyntax_JPEGProcess3_5; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess6_8TransferSyntax: -# else - case EXS_JPEGProcess6_8: -# endif - target = DicomTransferSyntax_JPEGProcess6_8; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess7_9TransferSyntax: -# else - case EXS_JPEGProcess7_9: -# endif - target = DicomTransferSyntax_JPEGProcess7_9; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess10_12TransferSyntax: -# else - case EXS_JPEGProcess10_12: -# endif - target = DicomTransferSyntax_JPEGProcess10_12; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess11_13TransferSyntax: -# else - case EXS_JPEGProcess11_13: -# endif - target = DicomTransferSyntax_JPEGProcess11_13; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess14TransferSyntax: -# else - case EXS_JPEGProcess14: -# endif - target = DicomTransferSyntax_JPEGProcess14; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess15TransferSyntax: -# else - case EXS_JPEGProcess15: -# endif - target = DicomTransferSyntax_JPEGProcess15; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess16_18TransferSyntax: -# else - case EXS_JPEGProcess16_18: -# endif - target = DicomTransferSyntax_JPEGProcess16_18; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess17_19TransferSyntax: -# else - case EXS_JPEGProcess17_19: -# endif - target = DicomTransferSyntax_JPEGProcess17_19; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess20_22TransferSyntax: -# else - case EXS_JPEGProcess20_22: -# endif - target = DicomTransferSyntax_JPEGProcess20_22; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess21_23TransferSyntax: -# else - case EXS_JPEGProcess21_23: -# endif - target = DicomTransferSyntax_JPEGProcess21_23; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess24_26TransferSyntax: -# else - case EXS_JPEGProcess24_26: -# endif - target = DicomTransferSyntax_JPEGProcess24_26; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess25_27TransferSyntax: -# else - case EXS_JPEGProcess25_27: -# endif - target = DicomTransferSyntax_JPEGProcess25_27; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess28TransferSyntax: -# else - case EXS_JPEGProcess28: -# endif - target = DicomTransferSyntax_JPEGProcess28; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess29TransferSyntax: -# else - case EXS_JPEGProcess29: -# endif - target = DicomTransferSyntax_JPEGProcess29; - return true; - -# if DCMTK_VERSION_NUMBER <= 360 - case EXS_JPEGProcess14SV1TransferSyntax: -# else - case EXS_JPEGProcess14SV1: -# endif - target = DicomTransferSyntax_JPEGProcess14SV1; - return true; - - case EXS_JPEGLSLossless: - target = DicomTransferSyntax_JPEGLSLossless; - return true; - - case EXS_JPEGLSLossy: - target = DicomTransferSyntax_JPEGLSLossy; - return true; - - case EXS_JPEG2000LosslessOnly: - target = DicomTransferSyntax_JPEG2000LosslessOnly; - return true; - - case EXS_JPEG2000: - target = DicomTransferSyntax_JPEG2000; - return true; - - case EXS_JPEG2000MulticomponentLosslessOnly: - target = DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly; - return true; - - case EXS_JPEG2000Multicomponent: - target = DicomTransferSyntax_JPEG2000Multicomponent; - return true; - - case EXS_JPIPReferenced: - target = DicomTransferSyntax_JPIPReferenced; - return true; - - case EXS_JPIPReferencedDeflate: - target = DicomTransferSyntax_JPIPReferencedDeflate; - return true; - - case EXS_MPEG2MainProfileAtMainLevel: - target = DicomTransferSyntax_MPEG2MainProfileAtMainLevel; - return true; - - case EXS_MPEG2MainProfileAtHighLevel: - target = DicomTransferSyntax_MPEG2MainProfileAtHighLevel; - return true; - -#if DCMTK_VERSION_NUMBER >= 361 - case EXS_MPEG4HighProfileLevel4_1: - target = DicomTransferSyntax_MPEG4HighProfileLevel4_1; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case EXS_MPEG4BDcompatibleHighProfileLevel4_1: - target = DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case EXS_MPEG4HighProfileLevel4_2_For2DVideo: - target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case EXS_MPEG4HighProfileLevel4_2_For3DVideo: - target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 361 - case EXS_MPEG4StereoHighProfileLevel4_2: - target = DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 362 - case EXS_HEVCMainProfileLevel5_1: - target = DicomTransferSyntax_HEVCMainProfileLevel5_1; - return true; -#endif - -#if DCMTK_VERSION_NUMBER >= 362 - case EXS_HEVCMain10ProfileLevel5_1: - target = DicomTransferSyntax_HEVCMain10ProfileLevel5_1; - return true; -#endif - - case EXS_RLELossless: - target = DicomTransferSyntax_RLELossless; - return true; - - default: - return false; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/IDicomTranscoder.cpp --- a/Core/DicomParsing/IDicomTranscoder.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,438 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "IDicomTranscoder.h" - -#include "../OrthancException.h" -#include "FromDcmtkBridge.h" -#include "ParsedDicomFile.h" - -#include -#include - -namespace Orthanc -{ - IDicomTranscoder::TranscodingType IDicomTranscoder::GetTranscodingType(DicomTransferSyntax target, - DicomTransferSyntax source) - { - if (target == source) - { - return TranscodingType_Lossless; - } - else if (target == DicomTransferSyntax_LittleEndianImplicit || - target == DicomTransferSyntax_LittleEndianExplicit || - target == DicomTransferSyntax_BigEndianExplicit || - target == DicomTransferSyntax_DeflatedLittleEndianExplicit || - target == DicomTransferSyntax_JPEGProcess14 || - target == DicomTransferSyntax_JPEGProcess14SV1 || - target == DicomTransferSyntax_JPEGLSLossless || - target == DicomTransferSyntax_JPEG2000LosslessOnly || - target == DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly) - { - return TranscodingType_Lossless; - } - else if (target == DicomTransferSyntax_JPEGProcess1 || - target == DicomTransferSyntax_JPEGProcess2_4 || - target == DicomTransferSyntax_JPEGLSLossy || - target == DicomTransferSyntax_JPEG2000 || - target == DicomTransferSyntax_JPEG2000Multicomponent) - { - return TranscodingType_Lossy; - } - else - { - return TranscodingType_Unknown; - } - } - - - std::string IDicomTranscoder::GetSopInstanceUid(DcmFileFormat& dicom) - { - if (dicom.getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - DcmDataset& dataset = *dicom.getDataset(); - - const char* v = NULL; - - if (dataset.findAndGetString(DCM_SOPInstanceUID, v).good() && - v != NULL) - { - return std::string(v); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat, "File without SOP instance UID"); - } - } - - - void IDicomTranscoder::CheckTranscoding(IDicomTranscoder::DicomImage& transcoded, - DicomTransferSyntax sourceSyntax, - const std::string& sourceSopInstanceUid, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) - { - DcmFileFormat& parsed = transcoded.GetParsed(); - - if (parsed.getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - std::string targetSopInstanceUid = GetSopInstanceUid(parsed); - - if (parsed.getDataset()->tagExists(DCM_PixelData)) - { - if (!allowNewSopInstanceUid && (targetSopInstanceUid != sourceSopInstanceUid)) - { - throw OrthancException(ErrorCode_InternalError); - } - } - else - { - if (targetSopInstanceUid != sourceSopInstanceUid) - { - throw OrthancException(ErrorCode_InternalError, - "No pixel data: Transcoding must not change the SOP instance UID"); - } - } - - DicomTransferSyntax targetSyntax; - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(targetSyntax, parsed)) - { - return; // Unknown transfer syntax, cannot do further test - } - - if (allowedSyntaxes.find(sourceSyntax) != allowedSyntaxes.end()) - { - // No transcoding should have happened - if (targetSopInstanceUid != sourceSopInstanceUid) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - if (allowedSyntaxes.find(targetSyntax) == allowedSyntaxes.end()) - { - throw OrthancException(ErrorCode_InternalError, "An incorrect output transfer syntax was chosen"); - } - - if (parsed.getDataset()->tagExists(DCM_PixelData)) - { - switch (GetTranscodingType(targetSyntax, sourceSyntax)) - { - case TranscodingType_Lossy: - if (targetSopInstanceUid == sourceSopInstanceUid) - { - throw OrthancException(ErrorCode_InternalError); - } - break; - - case TranscodingType_Lossless: - if (targetSopInstanceUid != sourceSopInstanceUid) - { - throw OrthancException(ErrorCode_InternalError); - } - break; - - default: - break; - } - } - } - - - void IDicomTranscoder::DicomImage::Parse() - { - if (parsed_.get() != NULL) - { - // Already parsed - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else if (buffer_.get() != NULL) - { - if (isExternalBuffer_) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - parsed_.reset(FromDcmtkBridge::LoadFromMemoryBuffer( - buffer_->empty() ? NULL : buffer_->c_str(), buffer_->size())); - - if (parsed_.get() == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - } - else if (isExternalBuffer_) - { - parsed_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(externalBuffer_, externalSize_)); - - if (parsed_.get() == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - else - { - // No buffer is available - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - void IDicomTranscoder::DicomImage::Serialize() - { - if (parsed_.get() == NULL || - buffer_.get() != NULL || - isExternalBuffer_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else if (parsed_->getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - buffer_.reset(new std::string); - FromDcmtkBridge::SaveToMemoryBuffer(*buffer_, *parsed_->getDataset()); - } - } - - - IDicomTranscoder::DicomImage::DicomImage() : - isExternalBuffer_(false) - { - } - - - void IDicomTranscoder::DicomImage::Clear() - { - parsed_.reset(NULL); - buffer_.reset(NULL); - isExternalBuffer_ = false; - } - - - void IDicomTranscoder::DicomImage::AcquireParsed(ParsedDicomFile& parsed) - { - AcquireParsed(parsed.ReleaseDcmtkObject()); - } - - - void IDicomTranscoder::DicomImage::AcquireParsed(DcmFileFormat* parsed) - { - if (parsed == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - else if (parsed->getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - else if (parsed_.get() != NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - parsed_.reset(parsed); - } - } - - - void IDicomTranscoder::DicomImage::AcquireParsed(DicomImage& other) - { - AcquireParsed(other.ReleaseParsed()); - } - - - void IDicomTranscoder::DicomImage::AcquireBuffer(std::string& buffer /* will be swapped */) - { - if (buffer_.get() != NULL || - isExternalBuffer_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - buffer_.reset(new std::string); - buffer_->swap(buffer); - } - } - - - void IDicomTranscoder::DicomImage::AcquireBuffer(DicomImage& other) - { - if (buffer_.get() != NULL || - isExternalBuffer_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else if (other.isExternalBuffer_) - { - assert(other.buffer_.get() == NULL); - isExternalBuffer_ = true; - externalBuffer_ = other.externalBuffer_; - externalSize_ = other.externalSize_; - } - else if (other.buffer_.get() != NULL) - { - buffer_.reset(other.buffer_.release()); - } - else - { - buffer_.reset(NULL); - } - } - - - void IDicomTranscoder::DicomImage::SetExternalBuffer(const void* buffer, - size_t size) - { - if (buffer_.get() != NULL || - isExternalBuffer_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - isExternalBuffer_ = true; - externalBuffer_ = buffer; - externalSize_ = size; - } - } - - - void IDicomTranscoder::DicomImage::SetExternalBuffer(const std::string& buffer) - { - SetExternalBuffer(buffer.empty() ? NULL : buffer.c_str(), buffer.size()); - } - - - DcmFileFormat& IDicomTranscoder::DicomImage::GetParsed() - { - if (parsed_.get() != NULL) - { - return *parsed_; - } - else if (buffer_.get() != NULL || - isExternalBuffer_) - { - Parse(); - return *parsed_; - } - else - { - throw OrthancException( - ErrorCode_BadSequenceOfCalls, - "AcquireParsed(), AcquireBuffer() or SetExternalBuffer() should have been called"); - } - } - - - DcmFileFormat* IDicomTranscoder::DicomImage::ReleaseParsed() - { - if (parsed_.get() != NULL) - { - buffer_.reset(NULL); - return parsed_.release(); - } - else if (buffer_.get() != NULL || - isExternalBuffer_) - { - Parse(); - buffer_.reset(NULL); - return parsed_.release(); - } - else - { - throw OrthancException( - ErrorCode_BadSequenceOfCalls, - "AcquireParsed(), AcquireBuffer() or SetExternalBuffer() should have been called"); - } - } - - - ParsedDicomFile* IDicomTranscoder::DicomImage::ReleaseAsParsedDicomFile() - { - return ParsedDicomFile::AcquireDcmtkObject(ReleaseParsed()); - } - - - const void* IDicomTranscoder::DicomImage::GetBufferData() - { - if (isExternalBuffer_) - { - assert(buffer_.get() == NULL); - return externalBuffer_; - } - else - { - if (buffer_.get() == NULL) - { - Serialize(); - } - - assert(buffer_.get() != NULL); - return buffer_->empty() ? NULL : buffer_->c_str(); - } - } - - - size_t IDicomTranscoder::DicomImage::GetBufferSize() - { - if (isExternalBuffer_) - { - assert(buffer_.get() == NULL); - return externalSize_; - } - else - { - if (buffer_.get() == NULL) - { - Serialize(); - } - - assert(buffer_.get() != NULL); - return buffer_->size(); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/IDicomTranscoder.h --- a/Core/DicomParsing/IDicomTranscoder.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Compatibility.h" -#include "../Enumerations.h" - -#include -#include - -class DcmFileFormat; - -namespace Orthanc -{ - class ParsedDicomFile; - - /** - * WARNING: This class might be called from several threads at - * once. Make sure to implement proper locking. - **/ - class ORTHANC_PUBLIC IDicomTranscoder : public boost::noncopyable - { - public: - class DicomImage : public boost::noncopyable - { - private: - std::unique_ptr parsed_; - std::unique_ptr buffer_; - bool isExternalBuffer_; - const void* externalBuffer_; - size_t externalSize_; - - void Parse(); - - void Serialize(); - - DcmFileFormat* ReleaseParsed(); - - public: - DicomImage(); - - void Clear(); - - // Calling this method will invalidate the "ParsedDicomFile" object - void AcquireParsed(ParsedDicomFile& parsed); - - void AcquireParsed(DcmFileFormat* parsed); - - void AcquireParsed(DicomImage& other); - - void AcquireBuffer(std::string& buffer /* will be swapped */); - - void AcquireBuffer(DicomImage& other); - - void SetExternalBuffer(const void* buffer, - size_t size); - - void SetExternalBuffer(const std::string& buffer); - - DcmFileFormat& GetParsed(); - - ParsedDicomFile* ReleaseAsParsedDicomFile(); - - const void* GetBufferData(); - - size_t GetBufferSize(); - }; - - - protected: - enum TranscodingType - { - TranscodingType_Lossy, - TranscodingType_Lossless, - TranscodingType_Unknown - }; - - static TranscodingType GetTranscodingType(DicomTransferSyntax target, - DicomTransferSyntax source); - - static void CheckTranscoding(DicomImage& transcoded, - DicomTransferSyntax sourceSyntax, - const std::string& sourceSopInstanceUid, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid); - - public: - virtual ~IDicomTranscoder() - { - } - - virtual bool Transcode(DicomImage& target, - DicomImage& source /* in, "GetParsed()" possibly modified */, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) = 0; - - static std::string GetSopInstanceUid(DcmFileFormat& dicom); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/ITagVisitor.h --- a/Core/DicomParsing/ITagVisitor.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../DicomFormat/DicomTag.h" - -#include -#include - -namespace Orthanc -{ - class ITagVisitor : public boost::noncopyable - { - public: - enum Action - { - Action_Replace, - Action_None - }; - - virtual ~ITagVisitor() - { - } - - // Visiting a DICOM element that is internal to DCMTK - virtual void VisitNotSupported(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr) = 0; - - // SQ - virtual void VisitEmptySequence(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag) = 0; - - // SL, SS, UL, US - virtual void VisitIntegers(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector& values) = 0; - - // FL, FD, OD, OF - virtual void VisitDoubles(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::vector& values) = 0; - - // AT - virtual void VisitAttributes(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - const std::vector& values) = 0; - - // OB, OL, OW, UN - virtual void VisitBinary(const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const void* data, - size_t size) = 0; - - // Visiting an UTF-8 string - virtual Action VisitString(std::string& newValue, - const std::vector& parentTags, - const std::vector& parentIndexes, - const DicomTag& tag, - ValueRepresentation vr, - const std::string& value) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/Internals/DicomFrameIndex.cpp --- a/Core/DicomParsing/Internals/DicomFrameIndex.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,403 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../../PrecompiledHeaders.h" -#include "DicomFrameIndex.h" - -#include "../../OrthancException.h" -#include "../../DicomFormat/DicomImageInformation.h" -#include "../FromDcmtkBridge.h" -#include "../../Endianness.h" -#include "DicomImageDecoder.h" - -#include - -#include -#include -#include - -namespace Orthanc -{ - class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex - { - private: - DcmPixelSequence* pixelSequence_; - std::vector startFragment_; - std::vector countFragments_; - std::vector frameSize_; - - void GetOffsetTable(std::vector& table) - { - DcmPixelItem* item = NULL; - if (!pixelSequence_->getItem(item, 0).good() || - item == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - uint32_t length = item->getLength(); - if (length == 0) - { - // Degenerate case: Empty offset table means only one frame - // that overlaps all the fragments - table.resize(1); - table[0] = 0; - return; - } - - if (length % 4 != 0) - { - // Error: Each fragment is index with 4 bytes (uint32_t) - throw OrthancException(ErrorCode_BadFileFormat); - } - - uint8_t* content = NULL; - if (!item->getUint8Array(content).good() || - content == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - table.resize(length / 4); - - // The offset table is always in little endian in the DICOM - // file. Swap it to host endianness if needed. - const uint32_t* offset = reinterpret_cast(content); - for (size_t i = 0; i < table.size(); i++, offset++) - { - table[i] = le32toh(*offset); - } - } - - - public: - FragmentIndex(DcmPixelSequence* pixelSequence, - unsigned int countFrames) : - pixelSequence_(pixelSequence) - { - assert(pixelSequence != NULL); - - startFragment_.resize(countFrames); - countFragments_.resize(countFrames); - frameSize_.resize(countFrames); - - // The first fragment corresponds to the offset table - unsigned int countFragments = static_cast(pixelSequence_->card()); - if (countFragments < countFrames + 1) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - if (countFragments == countFrames + 1) - { - // Simple case: There is one fragment per frame. - - DcmObject* fragment = pixelSequence_->nextInContainer(NULL); // Skip the offset table - if (fragment == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - for (unsigned int i = 0; i < countFrames; i++) - { - fragment = pixelSequence_->nextInContainer(fragment); - startFragment_[i] = dynamic_cast(fragment); - frameSize_[i] = fragment->getLength(); - countFragments_[i] = 1; - } - - return; - } - - // Parse the offset table - std::vector offsetOfFrame; - GetOffsetTable(offsetOfFrame); - - if (offsetOfFrame.size() != countFrames || - offsetOfFrame[0] != 0) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - // Loop over the fragments (ignoring the offset table). This is - // an alternative, faster implementation to DCMTK's - // "DcmCodec::determineStartFragment()". - DcmObject* fragment = pixelSequence_->nextInContainer(NULL); - if (fragment == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table - if (fragment == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - uint32_t offset = 0; - unsigned int currentFrame = 0; - startFragment_[0] = dynamic_cast(fragment); - - unsigned int currentFragment = 1; - while (fragment != NULL) - { - if (currentFrame + 1 < countFrames && - offset == offsetOfFrame[currentFrame + 1]) - { - currentFrame += 1; - startFragment_[currentFrame] = dynamic_cast(fragment); - } - - frameSize_[currentFrame] += fragment->getLength(); - countFragments_[currentFrame]++; - - // 8 bytes = overhead for the item tag and length field - offset += fragment->getLength() + 8; - - currentFragment++; - fragment = pixelSequence_->nextInContainer(fragment); - } - - if (currentFragment != countFragments || - currentFrame + 1 != countFrames || - fragment != NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - assert(startFragment_.size() == countFragments_.size() && - startFragment_.size() == frameSize_.size()); - } - - - virtual void GetRawFrame(std::string& frame, - unsigned int index) const - { - if (index >= startFragment_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - frame.resize(frameSize_[index]); - if (frame.size() == 0) - { - return; - } - - uint8_t* target = reinterpret_cast(&frame[0]); - - size_t offset = 0; - DcmPixelItem* fragment = startFragment_[index]; - for (unsigned int i = 0; i < countFragments_[index]; i++) - { - uint8_t* content = NULL; - if (!fragment->getUint8Array(content).good() || - content == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - assert(offset + fragment->getLength() <= frame.size()); - - memcpy(target + offset, content, fragment->getLength()); - offset += fragment->getLength(); - - fragment = dynamic_cast(pixelSequence_->nextInContainer(fragment)); - } - } - }; - - - - class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex - { - private: - uint8_t* pixelData_; - size_t frameSize_; - - public: - UncompressedIndex(DcmDataset& dataset, - unsigned int countFrames, - size_t frameSize) : - pixelData_(NULL), - frameSize_(frameSize) - { - size_t size = 0; - - DcmElement* e; - if (dataset.findAndGetElement(DCM_PixelData, e).good() && - e != NULL) - { - size = e->getLength(); - - if (size > 0) - { - pixelData_ = NULL; - if (!e->getUint8Array(pixelData_).good() || - pixelData_ == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - } - - if (size < frameSize_ * countFrames) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - virtual void GetRawFrame(std::string& frame, - unsigned int index) const - { - frame.resize(frameSize_); - if (frameSize_ > 0) - { - memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_); - } - } - }; - - - class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex - { - private: - std::string pixelData_; - size_t frameSize_; - - public: - PsmctRle1Index(DcmDataset& dataset, - unsigned int countFrames, - size_t frameSize) : - frameSize_(frameSize) - { - if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) || - pixelData_.size() < frameSize * countFrames) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - virtual void GetRawFrame(std::string& frame, - unsigned int index) const - { - frame.resize(frameSize_); - if (frameSize_ > 0) - { - memcpy(&frame[0], reinterpret_cast(&pixelData_[0]) + index * frameSize_, frameSize_); - } - } - }; - - - unsigned int DicomFrameIndex::GetFramesCount(DcmDataset& dicom) - { - const char* tmp = NULL; - if (!dicom.findAndGetString(DCM_NumberOfFrames, tmp).good() || - tmp == NULL) - { - return 1; - } - - int count = -1; - try - { - count = boost::lexical_cast(tmp); - } - catch (boost::bad_lexical_cast&) - { - } - - if (count < 0) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - return static_cast(count); - } - } - - - DicomFrameIndex::DicomFrameIndex(DcmDataset& dicom) - { - countFrames_ = GetFramesCount(dicom); - if (countFrames_ == 0) - { - // The image has no frame. No index is to be built. - return; - } - - // Test whether this image is composed of a sequence of fragments - DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dicom); - if (pixelSequence != NULL) - { - index_.reset(new FragmentIndex(pixelSequence, countFrames_)); - return; - } - - // Extract information about the image structure - DicomMap tags; - FromDcmtkBridge::ExtractDicomSummary(tags, dicom); - - DicomImageInformation information(tags); - - // Access to the raw pixel data - if (DicomImageDecoder::IsPsmctRle1(dicom)) - { - index_.reset(new PsmctRle1Index(dicom, countFrames_, information.GetFrameSize())); - } - else - { - index_.reset(new UncompressedIndex(dicom, countFrames_, information.GetFrameSize())); - } - } - - - void DicomFrameIndex::GetRawFrame(std::string& frame, - unsigned int index) const - { - if (index >= countFrames_) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else if (index_.get() != NULL) - { - return index_->GetRawFrame(frame, index); - } - else - { - frame.clear(); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/Internals/DicomFrameIndex.h --- a/Core/DicomParsing/Internals/DicomFrameIndex.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../../Compatibility.h" -#include "../../Enumerations.h" - -#include -#include -#include -#include -#include -#include - -namespace Orthanc -{ - class DicomFrameIndex - { - private: - class IIndex : public boost::noncopyable - { - public: - virtual ~IIndex() - { - } - - virtual void GetRawFrame(std::string& frame, - unsigned int index) const = 0; - }; - - class FragmentIndex; - class UncompressedIndex; - class PsmctRle1Index; - - std::unique_ptr index_; - unsigned int countFrames_; - - public: - DicomFrameIndex(DcmDataset& dicom); - - unsigned int GetFramesCount() const - { - return countFrames_; - } - - void GetRawFrame(std::string& frame, - unsigned int index) const; - - static unsigned int GetFramesCount(DcmDataset& dicom); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/Internals/DicomImageDecoder.cpp --- a/Core/DicomParsing/Internals/DicomImageDecoder.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1042 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../../PrecompiledHeaders.h" -#include "DicomImageDecoder.h" - -#include "../ParsedDicomFile.h" - - -/*========================================================================= - - This file is based on portions of the following project - (cf. function "DecodePsmctRle1()"): - - Program: GDCM (Grassroots DICOM). A DICOM library - Module: http://gdcm.sourceforge.net/Copyright.html - - Copyright (c) 2006-2011 Mathieu Malaterre - Copyright (c) 1993-2005 CREATIS - (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any - contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - =========================================================================*/ - - -#include "../../Logging.h" -#include "../../OrthancException.h" -#include "../../Images/Image.h" -#include "../../Images/ImageProcessing.h" -#include "../../DicomFormat/DicomIntegerPixelAccessor.h" -#include "../ToDcmtkBridge.h" -#include "../FromDcmtkBridge.h" - -#if ORTHANC_ENABLE_PNG == 1 -# include "../../Images/PngWriter.h" -#endif - -#if ORTHANC_ENABLE_JPEG == 1 -# include "../../Images/JpegWriter.h" -#endif -#include "../../Images/PamWriter.h" - -#include - -#include -#include -#include -#include - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 -# include -# include -# include -# include -#endif - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 -# include -# include -# include -# include -# include -# include -# include -# include -# include -#endif - -#if DCMTK_VERSION_NUMBER <= 360 -# define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax -# define EXS_JPEGProcess2_4 EXS_JPEGProcess2_4TransferSyntax -# define EXS_JPEGProcess6_8 EXS_JPEGProcess6_8TransferSyntax -# define EXS_JPEGProcess10_12 EXS_JPEGProcess10_12TransferSyntax -# define EXS_JPEGProcess14 EXS_JPEGProcess14TransferSyntax -# define EXS_JPEGProcess14SV1 EXS_JPEGProcess14SV1TransferSyntax -#endif - -namespace Orthanc -{ - static const DicomTag DICOM_TAG_CONTENT(0x07a1, 0x100a); - static const DicomTag DICOM_TAG_COMPRESSION_TYPE(0x07a1, 0x1011); - - - bool DicomImageDecoder::IsPsmctRle1(DcmDataset& dataset) - { - DcmElement* e; - char* c; - - // Check whether the DICOM instance contains an image encoded with - // the PMSCT_RLE1 scheme. - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_COMPRESSION_TYPE), e).good() || - e == NULL || - !e->isaString() || - !e->getString(c).good() || - c == NULL || - strcmp("PMSCT_RLE1", c)) - { - return false; - } - else - { - return true; - } - } - - - bool DicomImageDecoder::DecodePsmctRle1(std::string& output, - DcmDataset& dataset) - { - // Check whether the DICOM instance contains an image encoded with - // the PMSCT_RLE1 scheme. - if (!IsPsmctRle1(dataset)) - { - return false; - } - - // OK, this is a custom RLE encoding from Philips. Get the pixel - // data from the appropriate private DICOM tag. - Uint8* pixData = NULL; - DcmElement* e; - if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_CONTENT), e).good() || - e == NULL || - e->getUint8Array(pixData) != EC_Normal) - { - return false; - } - - // The "unsigned" below IS VERY IMPORTANT - const uint8_t* inbuffer = reinterpret_cast(pixData); - const size_t length = e->getLength(); - - /** - * The code below is an adaptation of a sample code for GDCM by - * Mathieu Malaterre (under a BSD license). - * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html - **/ - - // RLE pass - std::vector temp; - temp.reserve(length); - for (size_t i = 0; i < length; i++) - { - if (inbuffer[i] == 0xa5) - { - temp.push_back(inbuffer[i+2]); - for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--) - { - temp.push_back(inbuffer[i+2]); - } - i += 2; - } - else - { - temp.push_back(inbuffer[i]); - } - } - - // Delta encoding pass - uint16_t delta = 0; - output.clear(); - output.reserve(temp.size()); - for (size_t i = 0; i < temp.size(); i++) - { - uint16_t value; - - if (temp[i] == 0x5a) - { - uint16_t v1 = temp[i + 1]; - uint16_t v2 = temp[i + 2]; - value = (v2 << 8) + v1; - i += 2; - } - else - { - value = delta + (int8_t) temp[i]; - } - - output.push_back(value & 0xff); - output.push_back(value >> 8); - delta = value; - } - - if (output.size() % 2) - { - output.resize(output.size() - 1); - } - - return true; - } - - - class DicomImageDecoder::ImageSource - { - private: - std::string psmct_; - std::unique_ptr slowAccessor_; - - public: - void Setup(DcmDataset& dataset, - unsigned int frame) - { - psmct_.clear(); - slowAccessor_.reset(NULL); - - // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data - - DicomMap m; - FromDcmtkBridge::ExtractDicomSummary(m, dataset); - - /** - * Create an accessor to the raw values of the DICOM image. - **/ - - DcmElement* e; - if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() && - e != NULL) - { - Uint8* pixData = NULL; - if (e->getUint8Array(pixData) == EC_Normal) - { - slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength())); - } - } - else if (DecodePsmctRle1(psmct_, dataset)) - { - LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded"; - Uint8* pixData = NULL; - if (psmct_.size() > 0) - { - pixData = reinterpret_cast(&psmct_[0]); - } - - slowAccessor_.reset(new DicomIntegerPixelAccessor(m, pixData, psmct_.size())); - } - - if (slowAccessor_.get() == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - slowAccessor_->SetCurrentFrame(frame); - } - - unsigned int GetWidth() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetInformation().GetWidth(); - } - - unsigned int GetHeight() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetInformation().GetHeight(); - } - - unsigned int GetChannelCount() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetInformation().GetChannelCount(); - } - - const DicomIntegerPixelAccessor& GetAccessor() const - { - assert(slowAccessor_.get() != NULL); - return *slowAccessor_; - } - - unsigned int GetSize() const - { - assert(slowAccessor_.get() != NULL); - return slowAccessor_->GetSize(); - } - }; - - - ImageAccessor* DicomImageDecoder::CreateImage(DcmDataset& dataset, - bool ignorePhotometricInterpretation) - { - DicomMap m; - FromDcmtkBridge::ExtractDicomSummary(m, dataset); - - DicomImageInformation info(m); - PixelFormat format; - - if (!info.ExtractPixelFormat(format, ignorePhotometricInterpretation)) - { - LOG(WARNING) << "Unsupported DICOM image: " << info.GetBitsStored() - << "bpp, " << info.GetChannelCount() << " channels, " - << (info.IsSigned() ? "signed" : "unsigned") - << (info.IsPlanar() ? ", planar, " : ", non-planar, ") - << EnumerationToString(info.GetPhotometricInterpretation()) - << " photometric interpretation"; - throw OrthancException(ErrorCode_NotImplemented); - } - - return new Image(format, info.GetWidth(), info.GetHeight(), false); - } - - - template - static void CopyPixels(ImageAccessor& target, - const DicomIntegerPixelAccessor& source) - { - // WARNING - "::min()" should be replaced by "::lowest()" if - // dealing with float or double (which is not the case so far) - const PixelType minValue = std::numeric_limits::min(); - const PixelType maxValue = std::numeric_limits::max(); - - for (unsigned int y = 0; y < source.GetInformation().GetHeight(); y++) - { - PixelType* pixel = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < source.GetInformation().GetWidth(); x++) - { - for (unsigned int c = 0; c < source.GetInformation().GetChannelCount(); c++, pixel++) - { - int32_t v = source.GetValue(x, y, c); - if (v < static_cast(minValue)) - { - *pixel = minValue; - } - else if (v > static_cast(maxValue)) - { - *pixel = maxValue; - } - else - { - *pixel = static_cast(v); - } - } - } - } - } - - - static ImageAccessor* DecodeLookupTable(std::unique_ptr& target, - const DicomImageInformation& info, - DcmDataset& dataset, - const uint8_t* pixelData, - unsigned long pixelLength) - { - LOG(INFO) << "Decoding a lookup table"; - - OFString r, g, b; - PixelFormat format; - const uint16_t* lutRed = NULL; - const uint16_t* lutGreen = NULL; - const uint16_t* lutBlue = NULL; - unsigned long rc = 0; - unsigned long gc = 0; - unsigned long bc = 0; - - if (pixelData == NULL && - !dataset.findAndGetUint8Array(DCM_PixelData, pixelData, &pixelLength).good()) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - if (info.IsPlanar() || - info.GetNumberOfFrames() != 1 || - !info.ExtractPixelFormat(format, false) || - !dataset.findAndGetOFStringArray(DCM_BluePaletteColorLookupTableDescriptor, b).good() || - !dataset.findAndGetOFStringArray(DCM_GreenPaletteColorLookupTableDescriptor, g).good() || - !dataset.findAndGetOFStringArray(DCM_RedPaletteColorLookupTableDescriptor, r).good() || - !dataset.findAndGetUint16Array(DCM_BluePaletteColorLookupTableData, lutBlue, &bc).good() || - !dataset.findAndGetUint16Array(DCM_GreenPaletteColorLookupTableData, lutGreen, &gc).good() || - !dataset.findAndGetUint16Array(DCM_RedPaletteColorLookupTableData, lutRed, &rc).good() || - r != g || - r != b || - g != b || - lutRed == NULL || - lutGreen == NULL || - lutBlue == NULL || - pixelData == NULL) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - switch (format) - { - case PixelFormat_RGB24: - { - if (r != "256\\0\\16" || - rc != 256 || - gc != 256 || - bc != 256 || - pixelLength != target->GetWidth() * target->GetHeight()) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - const uint8_t* source = reinterpret_cast(pixelData); - - for (unsigned int y = 0; y < target->GetHeight(); y++) - { - uint8_t* p = reinterpret_cast(target->GetRow(y)); - - for (unsigned int x = 0; x < target->GetWidth(); x++) - { - p[0] = lutRed[*source] >> 8; - p[1] = lutGreen[*source] >> 8; - p[2] = lutBlue[*source] >> 8; - source++; - p += 3; - } - } - - return target.release(); - } - - case PixelFormat_RGB48: - { - if (r != "0\\0\\16" || - rc != 65536 || - gc != 65536 || - bc != 65536 || - pixelLength != 2 * target->GetWidth() * target->GetHeight()) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - const uint16_t* source = reinterpret_cast(pixelData); - - for (unsigned int y = 0; y < target->GetHeight(); y++) - { - uint16_t* p = reinterpret_cast(target->GetRow(y)); - - for (unsigned int x = 0; x < target->GetWidth(); x++) - { - p[0] = lutRed[*source]; - p[1] = lutGreen[*source]; - p[2] = lutBlue[*source]; - source++; - p += 3; - } - } - - return target.release(); - } - - default: - break; - } - - throw OrthancException(ErrorCode_InternalError); - } - - - ImageAccessor* DicomImageDecoder::DecodeUncompressedImage(DcmDataset& dataset, - unsigned int frame) - { - /** - * Create the target image. - **/ - - std::unique_ptr target(CreateImage(dataset, false)); - - ImageSource source; - source.Setup(dataset, frame); - - if (source.GetWidth() != target->GetWidth() || - source.GetHeight() != target->GetHeight()) - { - throw OrthancException(ErrorCode_InternalError); - } - - - /** - * Deal with lookup tables - **/ - - const DicomImageInformation& info = source.GetAccessor().GetInformation(); - - if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette) - { - return DecodeLookupTable(target, info, dataset, NULL, 0); - } - - - /** - * If the format of the DICOM buffer is natively supported, use a - * direct access to copy its values. - **/ - - bool fastVersionSuccess = false; - PixelFormat sourceFormat; - if (!info.IsPlanar() && - info.ExtractPixelFormat(sourceFormat, false)) - { - try - { - size_t frameSize = info.GetHeight() * info.GetWidth() * GetBytesPerPixel(sourceFormat); - if ((frame + 1) * frameSize <= source.GetSize()) - { - const uint8_t* buffer = reinterpret_cast(source.GetAccessor().GetPixelData()); - - ImageAccessor sourceImage; - sourceImage.AssignReadOnly(sourceFormat, - info.GetWidth(), - info.GetHeight(), - info.GetWidth() * GetBytesPerPixel(sourceFormat), - buffer + frame * frameSize); - - ImageProcessing::Convert(*target, sourceImage); - ImageProcessing::ShiftRight(*target, info.GetShift()); - fastVersionSuccess = true; - } - } - catch (OrthancException&) - { - // Unsupported conversion, use the slow version - } - } - - /** - * Slow version : loop over the DICOM buffer, storing its value - * into the target image. - **/ - - if (!fastVersionSuccess) - { - switch (target->GetFormat()) - { - case PixelFormat_RGB24: - case PixelFormat_RGBA32: - case PixelFormat_Grayscale8: - CopyPixels(*target, source.GetAccessor()); - break; - - case PixelFormat_Grayscale16: - CopyPixels(*target, source.GetAccessor()); - break; - - case PixelFormat_SignedGrayscale16: - CopyPixels(*target, source.GetAccessor()); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - return target.release(); - } - - - ImageAccessor* DicomImageDecoder::ApplyCodec - (const DcmCodec& codec, - const DcmCodecParameter& parameters, - const DcmRepresentationParameter& representationParameter, - DcmDataset& dataset, - unsigned int frame) - { - DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); - if (pixelSequence == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - DicomMap m; - FromDcmtkBridge::ExtractDicomSummary(m, dataset); - DicomImageInformation info(m); - - std::unique_ptr target(CreateImage(dataset, true)); - - Uint32 startFragment = 0; // Default - OFString decompressedColorModel; // Out - - OFCondition c; - - if (info.GetPhotometricInterpretation() == PhotometricInterpretation_Palette && - info.GetChannelCount() == 1) - { - std::string uncompressed; - uncompressed.resize(info.GetWidth() * info.GetHeight() * info.GetBytesPerValue()); - - if (uncompressed.size() == 0 || - !codec.decodeFrame(&representationParameter, - pixelSequence, ¶meters, - &dataset, frame, startFragment, &uncompressed[0], - uncompressed.size(), decompressedColorModel).good()) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot decode a palette image"); - } - - return DecodeLookupTable(target, info, dataset, - reinterpret_cast(uncompressed.c_str()), - uncompressed.size()); - } - else - { - if (!codec.decodeFrame(&representationParameter, - pixelSequence, ¶meters, - &dataset, frame, startFragment, target->GetBuffer(), - target->GetSize(), decompressedColorModel).good()) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot decode a non-palette image"); - } - - return target.release(); - } - } - - - ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom, - unsigned int frame) - { - if (dicom.GetDcmtkObject().getDataset() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - return Decode(*dicom.GetDcmtkObject().getDataset(), frame); - } - } - - - ImageAccessor* DicomImageDecoder::Decode(DcmDataset& dataset, - unsigned int frame) - { - E_TransferSyntax syntax = dataset.getCurrentXfer(); - - /** - * Deal with uncompressed, raw images. - * http://support.dcmtk.org/docs/dcxfer_8h-source.html - **/ - if (syntax == EXS_Unknown || - syntax == EXS_LittleEndianImplicit || - syntax == EXS_BigEndianImplicit || - syntax == EXS_LittleEndianExplicit || - syntax == EXS_BigEndianExplicit) - { - return DecodeUncompressedImage(dataset, frame); - } - - -#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1 - /** - * Deal with JPEG-LS images. - **/ - - if (syntax == EXS_JPEGLSLossless || - syntax == EXS_JPEGLSLossy) - { - // The (2, OFTrue) are the default parameters as found in DCMTK 3.6.2 - // http://support.dcmtk.org/docs/classDJLSRepresentationParameter.html - DJLSRepresentationParameter representationParameter(2, OFTrue); - - DJLSCodecParameter parameters; - std::unique_ptr decoder; - - switch (syntax) - { - case EXS_JPEGLSLossless: - LOG(INFO) << "Decoding a JPEG-LS lossless DICOM image"; - decoder.reset(new DJLSLosslessDecoder); - break; - - case EXS_JPEGLSLossy: - LOG(INFO) << "Decoding a JPEG-LS near-lossless DICOM image"; - decoder.reset(new DJLSNearLosslessDecoder); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame); - } -#endif - - -#if ORTHANC_ENABLE_DCMTK_JPEG == 1 - /** - * Deal with JPEG images. - **/ - - if (syntax == EXS_JPEGProcess1 || // DJDecoderBaseline - syntax == EXS_JPEGProcess2_4 || // DJDecoderExtended - syntax == EXS_JPEGProcess6_8 || // DJDecoderSpectralSelection (retired) - syntax == EXS_JPEGProcess10_12 || // DJDecoderProgressive (retired) - syntax == EXS_JPEGProcess14 || // DJDecoderLossless - syntax == EXS_JPEGProcess14SV1) // DJDecoderP14SV1 - { - // http://support.dcmtk.org/docs-snapshot/djutils_8h.html#a2a9695e5b6b0f5c45a64c7f072c1eb9d - DJCodecParameter parameters( - ECC_lossyYCbCr, // Mode for color conversion for compression, Unused for decompression - EDC_photometricInterpretation, // Perform color space conversion from YCbCr to RGB if DICOM photometric interpretation indicates YCbCr - EUC_default, // Mode for UID creation, unused for decompression - EPC_default); // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation - DJ_RPLossy representationParameter; - std::unique_ptr decoder; - - switch (syntax) - { - case EXS_JPEGProcess1: - LOG(INFO) << "Decoding a JPEG baseline (process 1) DICOM image"; - decoder.reset(new DJDecoderBaseline); - break; - - case EXS_JPEGProcess2_4 : - LOG(INFO) << "Decoding a JPEG baseline (processes 2 and 4) DICOM image"; - decoder.reset(new DJDecoderExtended); - break; - - case EXS_JPEGProcess6_8: // Retired - LOG(INFO) << "Decoding a JPEG spectral section, nonhierarchical (processes 6 and 8) DICOM image"; - decoder.reset(new DJDecoderSpectralSelection); - break; - - case EXS_JPEGProcess10_12: // Retired - LOG(INFO) << "Decoding a JPEG full progression, nonhierarchical (processes 10 and 12) DICOM image"; - decoder.reset(new DJDecoderProgressive); - break; - - case EXS_JPEGProcess14: - LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical (process 14) DICOM image"; - decoder.reset(new DJDecoderLossless); - break; - - case EXS_JPEGProcess14SV1: - LOG(INFO) << "Decoding a JPEG lossless, nonhierarchical, first-order prediction (process 14 selection value 1) DICOM image"; - decoder.reset(new DJDecoderP14SV1); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - return ApplyCodec(*decoder, parameters, representationParameter, dataset, frame); - } -#endif - - - if (syntax == EXS_RLELossless) - { - LOG(INFO) << "Decoding a RLE lossless DICOM image"; - DcmRLECodecParameter parameters; - DcmRLECodecDecoder decoder; - DcmRLERepresentationParameter representationParameter; - return ApplyCodec(decoder, parameters, representationParameter, dataset, frame); - } - - - /** - * This DICOM image format is not natively supported by - * Orthanc. As a last resort, try and decode it through DCMTK by - * converting its transfer syntax to Little Endian. This will - * result in higher memory consumption. This is actually the - * second example of the following page: - * http://support.dcmtk.org/docs/mod_dcmjpeg.html#Examples - **/ - - { - LOG(INFO) << "Trying to decode a compressed image by transcoding it to Little Endian Explicit"; - - std::unique_ptr converted(dynamic_cast(dataset.clone())); - converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL); - - if (converted->canWriteXfer(EXS_LittleEndianExplicit)) - { - return DecodeUncompressedImage(*converted, frame); - } - } - - DicomTransferSyntax s; - if (FromDcmtkBridge::LookupOrthancTransferSyntax(s, dataset.getCurrentXfer())) - { - throw OrthancException(ErrorCode_NotImplemented, - "The built-in DCMTK decoder cannot decode some DICOM instance " - "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s))); - } - else - { - throw OrthancException(ErrorCode_NotImplemented, - "The built-in DCMTK decoder cannot decode some DICOM instance"); - } - } - - - static bool IsColorImage(PixelFormat format) - { - return (format == PixelFormat_RGB24 || - format == PixelFormat_RGBA32); - } - - - bool DicomImageDecoder::TruncateDecodedImage(std::unique_ptr& image, - PixelFormat format, - bool allowColorConversion) - { - // If specified, prevent the conversion between color and - // grayscale images - bool isSourceColor = IsColorImage(image->GetFormat()); - bool isTargetColor = IsColorImage(format); - - if (!allowColorConversion) - { - if (isSourceColor ^ isTargetColor) - { - return false; - } - } - - if (image->GetFormat() != format) - { - // A conversion is required - std::unique_ptr target - (new Image(format, image->GetWidth(), image->GetHeight(), false)); - ImageProcessing::Convert(*target, *image); - -#if __cplusplus < 201103L - image.reset(target.release()); -#else - image = std::move(target); -#endif - } - - return true; - } - - - bool DicomImageDecoder::PreviewDecodedImage(std::unique_ptr& image) - { - switch (image->GetFormat()) - { - case PixelFormat_RGB24: - { - // Directly return color images without modification (RGB) - return true; - } - - case PixelFormat_RGB48: - { - std::unique_ptr target - (new Image(PixelFormat_RGB24, image->GetWidth(), image->GetHeight(), false)); - ImageProcessing::Convert(*target, *image); - -#if __cplusplus < 201103L - image.reset(target.release()); -#else - image = std::move(target); -#endif - - return true; - } - - case PixelFormat_Grayscale8: - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - { - // Grayscale image: Stretch its dynamics to the [0,255] range - int64_t a, b; - ImageProcessing::GetMinMaxIntegerValue(a, b, *image); - - if (a == b) - { - ImageProcessing::Set(*image, 0); - } - else - { - ImageProcessing::ShiftScale(*image, static_cast(-a), - 255.0f / static_cast(b - a), - true /* TODO - Consider using "false" to speed up */); - } - - // If the source image is not grayscale 8bpp, convert it - if (image->GetFormat() != PixelFormat_Grayscale8) - { - std::unique_ptr target - (new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false)); - ImageProcessing::Convert(*target, *image); - -#if __cplusplus < 201103L - image.reset(target.release()); -#else - image = std::move(target); -#endif - } - - return true; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void DicomImageDecoder::ApplyExtractionMode(std::unique_ptr& image, - ImageExtractionMode mode, - bool invert) - { - if (image.get() == NULL) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - bool ok = false; - - switch (mode) - { - case ImageExtractionMode_UInt8: - ok = TruncateDecodedImage(image, PixelFormat_Grayscale8, false); - break; - - case ImageExtractionMode_UInt16: - ok = TruncateDecodedImage(image, PixelFormat_Grayscale16, false); - break; - - case ImageExtractionMode_Int16: - ok = TruncateDecodedImage(image, PixelFormat_SignedGrayscale16, false); - break; - - case ImageExtractionMode_Preview: - ok = PreviewDecodedImage(image); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (ok) - { - assert(image.get() != NULL); - - if (invert) - { - Orthanc::ImageProcessing::Invert(*image); - } - } - else - { - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void DicomImageDecoder::ExtractPamImage(std::string& result, - std::unique_ptr& image, - ImageExtractionMode mode, - bool invert) - { - ApplyExtractionMode(image, mode, invert); - - PamWriter writer; - writer.WriteToMemory(result, *image); - } - -#if ORTHANC_ENABLE_PNG == 1 - void DicomImageDecoder::ExtractPngImage(std::string& result, - std::unique_ptr& image, - ImageExtractionMode mode, - bool invert) - { - ApplyExtractionMode(image, mode, invert); - - PngWriter writer; - writer.WriteToMemory(result, *image); - } -#endif - - -#if ORTHANC_ENABLE_JPEG == 1 - void DicomImageDecoder::ExtractJpegImage(std::string& result, - std::unique_ptr& image, - ImageExtractionMode mode, - bool invert, - uint8_t quality) - { - if (mode != ImageExtractionMode_UInt8 && - mode != ImageExtractionMode_Preview) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - ApplyExtractionMode(image, mode, invert); - - JpegWriter writer; - writer.SetQuality(quality); - writer.WriteToMemory(result, *image); - } -#endif -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/Internals/DicomImageDecoder.h --- a/Core/DicomParsing/Internals/DicomImageDecoder.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,130 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../../Compatibility.h" -#include "../../Images/ImageAccessor.h" - -#include - -#if !defined(ORTHANC_ENABLE_JPEG) -# error The macro ORTHANC_ENABLE_JPEG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_PNG) -# error The macro ORTHANC_ENABLE_PNG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG) -# error The macro ORTHANC_ENABLE_DCMTK_JPEG must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS) -# error The macro ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS must be defined -#endif - - -class DcmDataset; -class DcmCodec; -class DcmCodecParameter; -class DcmRepresentationParameter; - -namespace Orthanc -{ - class ParsedDicomFile; - - class DicomImageDecoder : public boost::noncopyable - { - private: - class ImageSource; - - DicomImageDecoder() // This is a fully abstract class, no constructor - { - } - - static ImageAccessor* CreateImage(DcmDataset& dataset, - bool ignorePhotometricInterpretation); - - static ImageAccessor* DecodeUncompressedImage(DcmDataset& dataset, - unsigned int frame); - - static ImageAccessor* ApplyCodec(const DcmCodec& codec, - const DcmCodecParameter& parameters, - const DcmRepresentationParameter& representationParameter, - DcmDataset& dataset, - unsigned int frame); - - static bool TruncateDecodedImage(std::unique_ptr& image, - PixelFormat format, - bool allowColorConversion); - - static bool PreviewDecodedImage(std::unique_ptr& image); - - static void ApplyExtractionMode(std::unique_ptr& image, - ImageExtractionMode mode, - bool invert); - - public: - static bool IsPsmctRle1(DcmDataset& dataset); - - static bool DecodePsmctRle1(std::string& output, - DcmDataset& dataset); - - static ImageAccessor *Decode(ParsedDicomFile& dicom, - unsigned int frame); - - static ImageAccessor *Decode(DcmDataset& dataset, - unsigned int frame); - - static void ExtractPamImage(std::string& result, - std::unique_ptr& image, - ImageExtractionMode mode, - bool invert); - -#if ORTHANC_ENABLE_PNG == 1 - static void ExtractPngImage(std::string& result, - std::unique_ptr& image, - ImageExtractionMode mode, - bool invert); -#endif - -#if ORTHANC_ENABLE_JPEG == 1 - static void ExtractJpegImage(std::string& result, - std::unique_ptr& image, - ImageExtractionMode mode, - bool invert, - uint8_t quality); -#endif - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/MemoryBufferTranscoder.cpp --- a/Core/DicomParsing/MemoryBufferTranscoder.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,109 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "MemoryBufferTranscoder.h" - -#include "../OrthancException.h" -#include "FromDcmtkBridge.h" - -#if !defined(NDEBUG) // For debugging -# include "ParsedDicomFile.h" -#endif - -namespace Orthanc -{ - static void CheckTargetSyntax(const std::string& transcoded, - const std::set& allowedSyntaxes) - { -#if !defined(NDEBUG) - // Debug mode - ParsedDicomFile parsed(transcoded); - - std::string s; - DicomTransferSyntax a, b; - if (!parsed.LookupTransferSyntax(s) || - !FromDcmtkBridge::LookupOrthancTransferSyntax(a, parsed.GetDcmtkObject()) || - !LookupTransferSyntax(b, s) || - a != b || - allowedSyntaxes.find(a) == allowedSyntaxes.end()) - { - throw OrthancException( - ErrorCode_Plugin, - "DEBUG - The transcoding plugin has not written to one of the allowed transfer syntaxes"); - } -#endif - } - - - bool MemoryBufferTranscoder::Transcode(DicomImage& target, - DicomImage& source, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) - { - target.Clear(); - -#if !defined(NDEBUG) - // Don't run this code in release mode, as it implies parsing the DICOM file - DicomTransferSyntax sourceSyntax; - if (!FromDcmtkBridge::LookupOrthancTransferSyntax(sourceSyntax, source.GetParsed())) - { - LOG(ERROR) << "Unsupport transfer syntax for transcoding"; - return false; - } - - const std::string sourceSopInstanceUid = GetSopInstanceUid(source.GetParsed()); -#endif - - std::string buffer; - if (TranscodeBuffer(buffer, source.GetBufferData(), source.GetBufferSize(), - allowedSyntaxes, allowNewSopInstanceUid)) - { - CheckTargetSyntax(buffer, allowedSyntaxes); // For debug only - - target.AcquireBuffer(buffer); - -#if !defined(NDEBUG) - // Only run the sanity check in debug mode - CheckTranscoding(target, sourceSyntax, sourceSopInstanceUid, - allowedSyntaxes, allowNewSopInstanceUid); -#endif - - return true; - } - else - { - return false; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/MemoryBufferTranscoder.h --- a/Core/DicomParsing/MemoryBufferTranscoder.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IDicomTranscoder.h" - -namespace Orthanc -{ - // This is the basis class for transcoding plugins - class MemoryBufferTranscoder : public IDicomTranscoder - { - protected: - virtual bool TranscodeBuffer(std::string& target, - const void* buffer, - size_t size, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) = 0; - - public: - virtual bool Transcode(DicomImage& target /* out */, - DicomImage& source, - const std::set& allowedSyntaxes, - bool allowNewSopInstanceUid) ORTHANC_OVERRIDE; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/ParsedDicomDir.cpp --- a/Core/DicomParsing/ParsedDicomDir.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "ParsedDicomDir.h" - -#include "../Compatibility.h" -#include "../OrthancException.h" -#include "ParsedDicomFile.h" -#include "FromDcmtkBridge.h" - -#include - - -namespace Orthanc -{ - void ParsedDicomDir::Clear() - { - for (size_t i = 0; i < content_.size(); i++) - { - assert(content_[i] != NULL); - delete content_[i]; - } - } - - - bool ParsedDicomDir::LookupIndexOfOffset(size_t& target, - unsigned int offset) const - { - if (offset == 0) - { - return false; - } - - OffsetToIndex::const_iterator found = offsetToIndex_.find(offset); - if (found == offsetToIndex_.end()) - { - // Error in the algorithm that computes the offsets - throw OrthancException(ErrorCode_InternalError); - } - else - { - target = found->second; - return true; - } - } - - - ParsedDicomDir::ParsedDicomDir(const std::string content) - { - ParsedDicomFile dicom(content); - - DcmSequenceOfItems* sequence = NULL; - if (dicom.GetDcmtkObject().getDataset() == NULL || - !dicom.GetDcmtkObject().getDataset()->findAndGetSequence(DCM_DirectoryRecordSequence, sequence).good() || - sequence == NULL) - { - throw OrthancException(ErrorCode_BadFileFormat, "Not a DICOMDIR"); - } - - content_.resize(sequence->card()); - nextOffsets_.resize(content_.size()); - lowerOffsets_.resize(content_.size()); - - // Manually reconstruct the list of all the available offsets of - // "DcmItem", as "fStartPosition" is a protected member in DCMTK - // API - std::set availableOffsets; - availableOffsets.insert(0); - - - for (unsigned long i = 0; i < sequence->card(); i++) - { - DcmItem* item = sequence->getItem(i); - if (item == NULL) - { - Clear(); - throw OrthancException(ErrorCode_InternalError); - } - - Uint32 next, lower; - if (!item->findAndGetUint32(DCM_OffsetOfTheNextDirectoryRecord, next).good() || - !item->findAndGetUint32(DCM_OffsetOfReferencedLowerLevelDirectoryEntity, lower).good()) - { - item->writeXML(std::cout); - throw OrthancException(ErrorCode_BadFileFormat, - "Missing offsets in DICOMDIR"); - } - - nextOffsets_[i] = next; - lowerOffsets_[i] = lower; - - std::unique_ptr entry(new DicomMap); - FromDcmtkBridge::ExtractDicomSummary(*entry, *item); - - if (next != 0) - { - availableOffsets.insert(next); - } - - if (lower != 0) - { - availableOffsets.insert(lower); - } - - content_[i] = entry.release(); - } - - if (content_.size() != availableOffsets.size()) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Inconsistent offsets in DICOMDIR"); - } - - unsigned int index = 0; - for (std::set::const_iterator it = availableOffsets.begin(); - it != availableOffsets.end(); ++it) - { - offsetToIndex_[*it] = index; - index ++; - } - } - - - const DicomMap& ParsedDicomDir::GetItem(size_t i) const - { - if (i >= content_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - assert(content_[i] != NULL); - return *content_[i]; - } - } - - - bool ParsedDicomDir::LookupNext(size_t& target, - size_t index) const - { - if (index >= nextOffsets_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - return LookupIndexOfOffset(target, nextOffsets_[index]); - } - } - - - bool ParsedDicomDir::LookupLower(size_t& target, - size_t index) const - { - if (index >= lowerOffsets_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - return LookupIndexOfOffset(target, lowerOffsets_[index]); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/ParsedDicomDir.h --- a/Core/DicomParsing/ParsedDicomDir.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if ORTHANC_ENABLE_DCMTK != 1 -# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 to use this file -#endif - -#include "../DicomFormat/DicomMap.h" - -namespace Orthanc -{ - class ParsedDicomDir : public boost::noncopyable - { - private: - typedef std::map OffsetToIndex; - - std::vector content_; - std::vector nextOffsets_; - std::vector lowerOffsets_; - OffsetToIndex offsetToIndex_; - - void Clear(); - - bool LookupIndexOfOffset(size_t& target, - unsigned int offset) const; - - public: - ParsedDicomDir(const std::string content); - - ~ParsedDicomDir() - { - Clear(); - } - - size_t GetSize() const - { - return content_.size(); - } - - const DicomMap& GetItem(size_t i) const; - - bool LookupNext(size_t& target, - size_t index) const; - - bool LookupLower(size_t& target, - size_t index) const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/ParsedDicomFile.cpp --- a/Core/DicomParsing/ParsedDicomFile.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1742 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - - -/*========================================================================= - - This file is based on portions of the following project: - - Program: GDCM (Grassroots DICOM). A DICOM library - Module: http://gdcm.sourceforge.net/Copyright.html - - Copyright (c) 2006-2011 Mathieu Malaterre - Copyright (c) 1993-2005 CREATIS - (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image) - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any - contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - =========================================================================*/ - - -#include "../PrecompiledHeaders.h" - -#ifndef NOMINMAX -#define NOMINMAX -#endif - -#include "ParsedDicomFile.h" - -#include "FromDcmtkBridge.h" -#include "Internals/DicomFrameIndex.h" -#include "ToDcmtkBridge.h" - -#include "../Images/PamReader.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "../Toolbox.h" - -#if ORTHANC_SANDBOXED == 0 -# include "../SystemToolbox.h" -#endif - -#if ORTHANC_ENABLE_JPEG == 1 -# include "../Images/JpegReader.h" -#endif - -#if ORTHANC_ENABLE_PNG == 1 -# include "../Images/PngReader.h" -#endif - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#include -#include -#include - - -#if DCMTK_VERSION_NUMBER <= 360 -# define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax -#endif - - - -namespace Orthanc -{ - struct ParsedDicomFile::PImpl - { - std::unique_ptr file_; - std::unique_ptr frameIndex_; - }; - - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 - static void ParseTagAndGroup(DcmTagKey& key, - const std::string& tag) - { - DicomTag t = FromDcmtkBridge::ParseTag(tag); - key = DcmTagKey(t.GetGroup(), t.GetElement()); - } - - - static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, - E_TransferSyntax transferSyntax) - { - DcmPixelSequence* pixelSequence = NULL; - if (pixelData.getEncapsulatedRepresentation - (transferSyntax, NULL, pixelSequence).good() && pixelSequence) - { - return pixelSequence->card(); - } - else - { - return 1; - } - } - - - static void SendPathValueForDictionary(RestApiOutput& output, - DcmItem& dicom) - { - Json::Value v = Json::arrayValue; - - for (unsigned long i = 0; i < dicom.card(); i++) - { - DcmElement* element = dicom.getElement(i); - if (element) - { - char buf[16]; - sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); - v.append(buf); - } - } - - output.AnswerJson(v); - } - - - static void SendSequence(RestApiOutput& output, - DcmSequenceOfItems& sequence) - { - // This element is a sequence - Json::Value v = Json::arrayValue; - - for (unsigned long i = 0; i < sequence.card(); i++) - { - v.append(boost::lexical_cast(i)); - } - - output.AnswerJson(v); - } - - - namespace - { - class DicomFieldStream : public IHttpStreamAnswer - { - private: - DcmElement& element_; - uint32_t length_; - uint32_t offset_; - std::string chunk_; - size_t chunkSize_; - - public: - DicomFieldStream(DcmElement& element, - E_TransferSyntax transferSyntax) : - element_(element), - length_(element.getLength(transferSyntax)), - offset_(0), - chunkSize_(0) - { - static const size_t CHUNK_SIZE = 64 * 1024; // Use chunks of max 64KB - chunk_.resize(CHUNK_SIZE); - } - - virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, - bool /*deflateAllowed*/) - ORTHANC_OVERRIDE - { - // No support for compression - return HttpCompression_None; - } - - virtual bool HasContentFilename(std::string& filename) ORTHANC_OVERRIDE - { - return false; - } - - virtual std::string GetContentType() ORTHANC_OVERRIDE - { - return EnumerationToString(MimeType_Binary); - } - - virtual uint64_t GetContentLength() ORTHANC_OVERRIDE - { - return length_; - } - - virtual bool ReadNextChunk() ORTHANC_OVERRIDE - { - assert(offset_ <= length_); - - if (offset_ == length_) - { - return false; - } - else - { - if (length_ - offset_ < chunk_.size()) - { - chunkSize_ = length_ - offset_; - } - else - { - chunkSize_ = chunk_.size(); - } - - OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_); - - offset_ += chunkSize_; - - if (!cond.good()) - { - throw OrthancException(ErrorCode_InternalError, - "Error while sending a DICOM field: " + - std::string(cond.text())); - } - - return true; - } - } - - virtual const char *GetChunkContent() ORTHANC_OVERRIDE - { - return chunk_.c_str(); - } - - virtual size_t GetChunkSize() ORTHANC_OVERRIDE - { - return chunkSize_; - } - }; - } - - - static bool AnswerPixelData(RestApiOutput& output, - DcmItem& dicom, - E_TransferSyntax transferSyntax, - const std::string* blockUri) - { - DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), - DICOM_TAG_PIXEL_DATA.GetElement()); - - DcmElement *element = NULL; - if (!dicom.findAndGetElement(k, element).good() || - element == NULL) - { - return false; - } - - try - { - DcmPixelData& pixelData = dynamic_cast(*element); - if (blockUri == NULL) - { - // The user asks how many blocks are present in this pixel data - unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); - - Json::Value result(Json::arrayValue); - for (unsigned int i = 0; i < blocks; i++) - { - result.append(boost::lexical_cast(i)); - } - - output.AnswerJson(result); - return true; - } - - - unsigned int block = boost::lexical_cast(*blockUri); - - if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) - { - DcmPixelSequence* pixelSequence = NULL; - if (pixelData.getEncapsulatedRepresentation - (transferSyntax, NULL, pixelSequence).good() && pixelSequence) - { - // This is the case for JPEG transfer syntaxes - if (block < pixelSequence->card()) - { - DcmPixelItem* pixelItem = NULL; - if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) - { - if (pixelItem->getLength() == 0) - { - output.AnswerBuffer(NULL, 0, MimeType_Binary); - return true; - } - - Uint8* buffer = NULL; - if (pixelItem->getUint8Array(buffer).good() && buffer) - { - output.AnswerBuffer(buffer, pixelItem->getLength(), MimeType_Binary); - return true; - } - } - } - } - else - { - // This is the case for raw, uncompressed image buffers - assert(*blockUri == "0"); - DicomFieldStream stream(*element, transferSyntax); - output.AnswerStream(stream); - } - } - } - catch (boost::bad_lexical_cast&) - { - // The URI entered by the user is not a number - } - catch (std::bad_cast&) - { - // This should never happen - } - - return false; - } - - - static void SendPathValueForLeaf(RestApiOutput& output, - const std::string& tag, - DcmItem& dicom, - E_TransferSyntax transferSyntax) - { - DcmTagKey k; - ParseTagAndGroup(k, tag); - - DcmSequenceOfItems* sequence = NULL; - if (dicom.findAndGetSequence(k, sequence).good() && - sequence != NULL && - sequence->getVR() == EVR_SQ) - { - SendSequence(output, *sequence); - return; - } - - DcmElement* element = NULL; - if (dicom.findAndGetElement(k, element).good() && - element != NULL && - //element->getVR() != EVR_UNKNOWN && // This would forbid private tags - element->getVR() != EVR_SQ) - { - DicomFieldStream stream(*element, transferSyntax); - output.AnswerStream(stream); - } - } -#endif - - - static inline uint16_t GetCharValue(char c) - { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else - return 0; - } - - - static inline uint16_t GetTagValue(const char* c) - { - return ((GetCharValue(c[0]) << 12) + - (GetCharValue(c[1]) << 8) + - (GetCharValue(c[2]) << 4) + - GetCharValue(c[3])); - } - - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 - void ParsedDicomFile::SendPathValue(RestApiOutput& output, - const UriComponents& uri) - { - DcmItem* dicom = GetDcmtkObject().getDataset(); - E_TransferSyntax transferSyntax = GetDcmtkObject().getDataset()->getCurrentXfer(); - - // Special case: Accessing the pixel data - if (uri.size() == 1 || - uri.size() == 2) - { - DcmTagKey tag; - ParseTagAndGroup(tag, uri[0]); - - if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && - tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) - { - AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); - return; - } - } - - // Go down in the tag hierarchy according to the URI - for (size_t pos = 0; pos < uri.size() / 2; pos++) - { - size_t index; - try - { - index = boost::lexical_cast(uri[2 * pos + 1]); - } - catch (boost::bad_lexical_cast&) - { - return; - } - - DcmTagKey k; - DcmItem *child = NULL; - ParseTagAndGroup(k, uri[2 * pos]); - if (!dicom->findAndGetSequenceItem(k, child, index).good() || - child == NULL) - { - return; - } - - dicom = child; - } - - // We have reached the end of the URI - if (uri.size() % 2 == 0) - { - SendPathValueForDictionary(output, *dicom); - } - else - { - SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); - } - } -#endif - - - void ParsedDicomFile::Remove(const DicomTag& tag) - { - InvalidateCache(); - - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - DcmElement* element = GetDcmtkObject().getDataset()->remove(key); - if (element != NULL) - { - delete element; - } - } - - - void ParsedDicomFile::Clear(const DicomTag& tag, - bool onlyIfExists) - { - if (tag.GetElement() == 0x0000) - { - // Prevent manually modifying generic group length tags: This is - // handled by DCMTK serialization - return; - } - - InvalidateCache(); - - DcmItem* dicom = GetDcmtkObject().getDataset(); - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - - if (onlyIfExists && - !dicom->tagExists(key)) - { - // The tag is non-existing, do not clear it - } - else - { - if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - } - - - void ParsedDicomFile::RemovePrivateTagsInternal(const std::set* toKeep) - { - InvalidateCache(); - - DcmDataset& dataset = *GetDcmtkObject().getDataset(); - - // Loop over the dataset to detect its private tags - typedef std::list Tags; - Tags privateTags; - - for (unsigned long i = 0; i < dataset.card(); i++) - { - DcmElement* element = dataset.getElement(i); - DcmTag tag(element->getTag()); - - // Is this a private tag? - if (tag.isPrivate()) - { - bool remove = true; - - // Check whether this private tag is to be kept - if (toKeep != NULL) - { - DicomTag tmp = FromDcmtkBridge::Convert(tag); - if (toKeep->find(tmp) != toKeep->end()) - { - remove = false; // Keep it - } - } - - if (remove) - { - privateTags.push_back(element); - } - } - } - - // Loop over the detected private tags to remove them - for (Tags::iterator it = privateTags.begin(); - it != privateTags.end(); ++it) - { - DcmElement* tmp = dataset.remove(*it); - if (tmp != NULL) - { - delete tmp; - } - } - } - - - static void InsertInternal(DcmDataset& dicom, - DcmElement* element) - { - OFCondition cond = dicom.insert(element, false, false); - if (!cond.good()) - { - // This field already exists - delete element; - throw OrthancException(ErrorCode_InternalError); - } - } - - - void ParsedDicomFile::Insert(const DicomTag& tag, - const Json::Value& value, - bool decodeDataUriScheme, - const std::string& privateCreator) - { - if (tag.GetElement() == 0x0000) - { - // Prevent manually modifying generic group length tags: This is - // handled by DCMTK serialization - return; - } - - if (GetDcmtkObject().getDataset()->tagExists(ToDcmtkBridge::Convert(tag))) - { - throw OrthancException(ErrorCode_AlreadyExistingTag); - } - - if (decodeDataUriScheme && - value.type() == Json::stringValue && - (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || - tag == DICOM_TAG_PIXEL_DATA)) - { - if (EmbedContentInternal(value.asString())) - { - return; - } - } - - InvalidateCache(); - - bool hasCodeExtensions; - Encoding encoding = DetectEncoding(hasCodeExtensions); - std::unique_ptr element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); - InsertInternal(*GetDcmtkObject().getDataset(), element.release()); - } - - - void ParsedDicomFile::ReplacePlainString(const DicomTag& tag, - const std::string& utf8Value) - { - if (tag.IsPrivate()) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot apply this function to private tags: " + tag.Format()); - } - else - { - Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent, - "" /* not a private tag, so no private creator */); - } - } - - - void ParsedDicomFile::SetIfAbsent(const DicomTag& tag, - const std::string& utf8Value) - { - std::string currentValue; - if (!GetTagValue(currentValue, tag)) - { - ReplacePlainString(tag, utf8Value); - } - } - - - static bool CanReplaceProceed(DcmDataset& dicom, - const DcmTagKey& tag, - DicomReplaceMode mode) - { - if (dicom.findAndDeleteElement(tag).good()) - { - // This tag was existing, it has been deleted - return true; - } - else - { - // This tag was absent, act wrt. the specified "mode" - switch (mode) - { - case DicomReplaceMode_InsertIfAbsent: - return true; - - case DicomReplaceMode_ThrowIfAbsent: - throw OrthancException(ErrorCode_InexistentItem); - - case DicomReplaceMode_IgnoreIfAbsent: - return false; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - } - - - void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag, - const std::string& utf8Value, - bool decodeDataUriScheme) - { - if (tag != DICOM_TAG_SOP_CLASS_UID && - tag != DICOM_TAG_SOP_INSTANCE_UID) - { - return; - } - - std::string binary; - const std::string* decoded = &utf8Value; - - if (decodeDataUriScheme && - boost::starts_with(utf8Value, URI_SCHEME_PREFIX_BINARY)) - { - std::string mime; - if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - decoded = &binary; - } - else - { - bool hasCodeExtensions; - Encoding encoding = DetectEncoding(hasCodeExtensions); - if (encoding != Encoding_Utf8) - { - binary = Toolbox::ConvertFromUtf8(utf8Value, encoding); - decoded = &binary; - } - } - - /** - * dcmodify will automatically correct 'Media Storage SOP Class - * UID' and 'Media Storage SOP Instance UID' in the metaheader, if - * you make changes to the related tags in the dataset ('SOP Class - * UID' and 'SOP Instance UID') via insert or modify mode - * options. You can disable this behaviour by using the -nmu - * option. - **/ - - if (tag == DICOM_TAG_SOP_CLASS_UID) - { - ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded); - } - - if (tag == DICOM_TAG_SOP_INSTANCE_UID) - { - ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded); - } - } - - - void ParsedDicomFile::Replace(const DicomTag& tag, - const std::string& utf8Value, - bool decodeDataUriScheme, - DicomReplaceMode mode, - const std::string& privateCreator) - { - if (tag.GetElement() == 0x0000) - { - // Prevent manually modifying generic group length tags: This is - // handled by DCMTK serialization - return; - } - - InvalidateCache(); - - DcmDataset& dicom = *GetDcmtkObject().getDataset(); - if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) - { - // Either the tag was previously existing (and now removed), or - // the replace mode was set to "InsertIfAbsent" - - if (decodeDataUriScheme && - (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || - tag == DICOM_TAG_PIXEL_DATA)) - { - if (EmbedContentInternal(utf8Value)) - { - return; - } - } - - std::unique_ptr element(FromDcmtkBridge::CreateElementForTag(tag, privateCreator)); - - if (!utf8Value.empty()) - { - bool hasCodeExtensions; - Encoding encoding = DetectEncoding(hasCodeExtensions); - FromDcmtkBridge::FillElementWithString(*element, utf8Value, decodeDataUriScheme, encoding); - } - - InsertInternal(dicom, element.release()); - UpdateStorageUid(tag, utf8Value, false); - } - } - - - void ParsedDicomFile::Replace(const DicomTag& tag, - const Json::Value& value, - bool decodeDataUriScheme, - DicomReplaceMode mode, - const std::string& privateCreator) - { - if (tag.GetElement() == 0x0000) - { - // Prevent manually modifying generic group length tags: This is - // handled by DCMTK serialization - return; - } - - InvalidateCache(); - - DcmDataset& dicom = *GetDcmtkObject().getDataset(); - if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode)) - { - // Either the tag was previously existing (and now removed), or - // the replace mode was set to "InsertIfAbsent" - - if (decodeDataUriScheme && - value.type() == Json::stringValue && - (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT || - tag == DICOM_TAG_PIXEL_DATA)) - { - if (EmbedContentInternal(value.asString())) - { - return; - } - } - - bool hasCodeExtensions; - Encoding encoding = DetectEncoding(hasCodeExtensions); - InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator)); - - if (tag == DICOM_TAG_SOP_CLASS_UID || - tag == DICOM_TAG_SOP_INSTANCE_UID) - { - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - UpdateStorageUid(tag, value.asString(), decodeDataUriScheme); - } - } - } - - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 - void ParsedDicomFile::Answer(RestApiOutput& output) - { - std::string serialized; - if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *GetDcmtkObject().getDataset())) - { - output.AnswerBuffer(serialized, MimeType_Dicom); - } - } -#endif - - - bool ParsedDicomFile::GetTagValue(std::string& value, - const DicomTag& tag) - { - DcmTagKey k(tag.GetGroup(), tag.GetElement()); - DcmDataset& dataset = *GetDcmtkObject().getDataset(); - - if (tag.IsPrivate() || - FromDcmtkBridge::IsUnknownTag(tag) || - tag == DICOM_TAG_PIXEL_DATA || - tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) - { - const Uint8* data = NULL; // This is freed in the destructor of the dataset - long unsigned int count = 0; - - if (dataset.findAndGetUint8Array(k, data, &count).good()) - { - if (count > 0) - { - assert(data != NULL); - value.assign(reinterpret_cast(data), count); - } - else - { - value.clear(); - } - - return true; - } - else - { - return false; - } - } - else - { - DcmElement* element = NULL; - if (!dataset.findAndGetElement(k, element).good() || - element == NULL) - { - return false; - } - - bool hasCodeExtensions; - Encoding encoding = DetectEncoding(hasCodeExtensions); - - std::set tmp; - std::unique_ptr v(FromDcmtkBridge::ConvertLeafElement - (*element, DicomToJsonFlags_Default, - 0, encoding, hasCodeExtensions, tmp)); - - if (v.get() == NULL || - v->IsNull()) - { - value = ""; - } - else - { - // TODO v->IsBinary() - value = v->GetContent(); - } - - return true; - } - } - - - DicomInstanceHasher ParsedDicomFile::GetHasher() - { - std::string patientId, studyUid, seriesUid, instanceUid; - - if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID)) - { - /** - * If "PatientID" is absent, be tolerant by considering it - * equals the empty string, then proceed. In Orthanc <= 1.5.6, - * an exception "Bad file format" was generated. - * https://groups.google.com/d/msg/orthanc-users/aphG_h1AHVg/rfOTtTPTAgAJ - * https://hg.orthanc-server.com/orthanc/rev/4c45e018bd3de3cfa21d6efc6734673aaaee4435 - **/ - patientId.clear(); - } - - if (!GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) || - !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) || - !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID)) - { - throw OrthancException(ErrorCode_BadFileFormat, "missing StudyInstanceUID, SeriesInstanceUID or SOPInstanceUID"); - } - - return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid); - } - - - void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer) - { - FromDcmtkBridge::SaveToMemoryBuffer(buffer, *GetDcmtkObject().getDataset()); - } - - -#if ORTHANC_SANDBOXED == 0 - void ParsedDicomFile::SaveToFile(const std::string& path) - { - // TODO Avoid using a temporary memory buffer, write directly on disk - std::string content; - SaveToMemoryBuffer(content); - SystemToolbox::WriteFile(content, path); - } -#endif - - - ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl) - { - pimpl_->file_.reset(new DcmFileFormat); - - if (createIdentifiers) - { - ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient)); - ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)); - ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series)); - ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); - } - } - - - void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source, - Encoding defaultEncoding, - bool permissive, - const std::string& defaultPrivateCreator, - const std::map& privateCreators) - { - pimpl_->file_.reset(new DcmFileFormat); - InvalidateCache(); - - const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET); - - if (tmp == NULL) - { - SetEncoding(defaultEncoding); - } - else if (tmp->IsBinary()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Invalid binary string in the SpecificCharacterSet (0008,0005) tag"); - } - else if (tmp->IsNull() || - tmp->GetContent().empty()) - { - SetEncoding(defaultEncoding); - } - else - { - Encoding encoding; - - if (GetDicomEncoding(encoding, tmp->GetContent().c_str())) - { - SetEncoding(encoding); - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Unsupported value for the SpecificCharacterSet (0008,0005) tag: \"" + - tmp->GetContent() + "\""); - } - } - - for (DicomMap::Content::const_iterator - it = source.content_.begin(); it != source.content_.end(); ++it) - { - if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET && - !it->second->IsNull()) - { - try - { - // Same as "ReplacePlainString()", but with support for private creator - const std::string& utf8Value = it->second->GetContent(); - - std::map::const_iterator found = privateCreators.find(it->first.GetGroup()); - - if (it->first.IsPrivate() && - found != privateCreators.end()) - { - Replace(it->first, utf8Value, false, DicomReplaceMode_InsertIfAbsent, found->second); - } - else - { - Replace(it->first, utf8Value, false, DicomReplaceMode_InsertIfAbsent, defaultPrivateCreator); - } - } - catch (OrthancException&) - { - if (!permissive) - { - throw; - } - } - } - } - } - - ParsedDicomFile::ParsedDicomFile(const DicomMap& map, - Encoding defaultEncoding, - bool permissive) : - pimpl_(new PImpl) - { - std::map noPrivateCreators; - CreateFromDicomMap(map, defaultEncoding, permissive, "" /* no default private creator */, noPrivateCreators); - } - - - ParsedDicomFile::ParsedDicomFile(const DicomMap& map, - Encoding defaultEncoding, - bool permissive, - const std::string& defaultPrivateCreator, - const std::map& privateCreators) : - pimpl_(new PImpl) - { - CreateFromDicomMap(map, defaultEncoding, permissive, defaultPrivateCreator, privateCreators); - } - - - ParsedDicomFile::ParsedDicomFile(const void* content, - size_t size) : pimpl_(new PImpl) - { - pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(content, size)); - } - - ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl) - { - if (content.size() == 0) - { - pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(NULL, 0)); - } - else - { - pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(&content[0], content.size())); - } - } - - - ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other, - bool keepSopInstanceUid) : - pimpl_(new PImpl) - { - pimpl_->file_.reset(dynamic_cast(other.GetDcmtkObject().clone())); - - if (!keepSopInstanceUid) - { - // Create a new instance-level identifier - ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance)); - } - } - - - ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl) - { - pimpl_->file_.reset(new DcmFileFormat(&dicom)); - } - - - ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl) - { - pimpl_->file_.reset(new DcmFileFormat(dicom)); - } - - - ParsedDicomFile::ParsedDicomFile(DcmFileFormat* dicom) : pimpl_(new PImpl) - { - pimpl_->file_.reset(dicom); // No cloning - } - - - DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const - { - if (pimpl_->file_.get() == NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "ReleaseDcmtkObject() was called"); - } - else - { - return *pimpl_->file_; - } - } - - - DcmFileFormat* ParsedDicomFile::ReleaseDcmtkObject() - { - if (pimpl_->file_.get() == NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "ReleaseDcmtkObject() was called"); - } - else - { - pimpl_->frameIndex_.reset(NULL); - return pimpl_->file_.release(); - } - } - - - ParsedDicomFile* ParsedDicomFile::Clone(bool keepSopInstanceUid) - { - return new ParsedDicomFile(*this, keepSopInstanceUid); - } - - - bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme) - { - std::string mimeString, content; - if (!Toolbox::DecodeDataUriScheme(mimeString, content, dataUriScheme)) - { - return false; - } - - Toolbox::ToLowerCase(mimeString); - MimeType mime = StringToMimeType(mimeString); - - switch (mime) - { - case MimeType_Png: -#if ORTHANC_ENABLE_PNG == 1 - EmbedImage(mime, content); - break; -#else - throw OrthancException(ErrorCode_NotImplemented, - "Orthanc was compiled without support of PNG"); -#endif - - case MimeType_Jpeg: -#if ORTHANC_ENABLE_JPEG == 1 - EmbedImage(mime, content); - break; -#else - throw OrthancException(ErrorCode_NotImplemented, - "Orthanc was compiled without support of JPEG"); -#endif - - case MimeType_Pam: - EmbedImage(mime, content); - break; - - case MimeType_Pdf: - EmbedPdf(content); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented, - "Unsupported MIME type for the content of a new DICOM file: " + - std::string(EnumerationToString(mime))); - } - - return true; - } - - - void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme) - { - if (!EmbedContentInternal(dataUriScheme)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - void ParsedDicomFile::EmbedImage(MimeType mime, - const std::string& content) - { - switch (mime) - { - -#if ORTHANC_ENABLE_JPEG == 1 - case MimeType_Jpeg: - { - JpegReader reader; - reader.ReadFromMemory(content); - EmbedImage(reader); - break; - } -#endif - -#if ORTHANC_ENABLE_PNG == 1 - case MimeType_Png: - { - PngReader reader; - reader.ReadFromMemory(content); - EmbedImage(reader); - break; - } -#endif - - case MimeType_Pam: - { - PamReader reader; - reader.ReadFromMemory(content); - EmbedImage(reader); - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor) - { - if (accessor.GetFormat() != PixelFormat_Grayscale8 && - accessor.GetFormat() != PixelFormat_Grayscale16 && - accessor.GetFormat() != PixelFormat_SignedGrayscale16 && - accessor.GetFormat() != PixelFormat_RGB24 && - accessor.GetFormat() != PixelFormat_RGBA32) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - InvalidateCache(); - - if (accessor.GetFormat() == PixelFormat_RGBA32) - { - LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM"; - } - - // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html - - Remove(DICOM_TAG_PIXEL_DATA); - ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast(accessor.GetWidth())); - ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast(accessor.GetHeight())); - ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1"); - - // The "Number of frames" must only be present in multi-frame images - //ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1"); - - if (accessor.GetFormat() == PixelFormat_SignedGrayscale16) - { - ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1"); - } - else - { - ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0"); // Unsigned pixels - } - - unsigned int bytesPerPixel = 0; - - switch (accessor.GetFormat()) - { - case PixelFormat_Grayscale8: - // By default, grayscale images are MONOCHROME2 - SetIfAbsent(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); - - ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); - ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); - ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); - bytesPerPixel = 1; - break; - - case PixelFormat_RGB24: - case PixelFormat_RGBA32: - ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB"); - ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3"); - ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8"); - ReplacePlainString(DICOM_TAG_BITS_STORED, "8"); - ReplacePlainString(DICOM_TAG_HIGH_BIT, "7"); - bytesPerPixel = 3; - - // "Planar configuration" must only present if "Samples per - // Pixel" is greater than 1 - ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved - - break; - - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - // By default, grayscale images are MONOCHROME2 - SetIfAbsent(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2"); - - ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16"); - ReplacePlainString(DICOM_TAG_BITS_STORED, "16"); - ReplacePlainString(DICOM_TAG_HIGH_BIT, "15"); - bytesPerPixel = 2; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - assert(bytesPerPixel != 0); - - DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), - DICOM_TAG_PIXEL_DATA.GetElement()); - - std::unique_ptr pixels(new DcmPixelData(key)); - - unsigned int pitch = accessor.GetWidth() * bytesPerPixel; - Uint8* target = NULL; - pixels->createUint8Array(accessor.GetHeight() * pitch, target); - - for (unsigned int y = 0; y < accessor.GetHeight(); y++) - { - switch (accessor.GetFormat()) - { - case PixelFormat_RGB24: - case PixelFormat_Grayscale8: - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - { - memcpy(target, reinterpret_cast(accessor.GetConstRow(y)), pitch); - target += pitch; - break; - } - - case PixelFormat_RGBA32: - { - // The alpha channel is not supported by the DICOM standard - const Uint8* source = reinterpret_cast(accessor.GetConstRow(y)); - for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4) - { - target[0] = source[0]; - target[1] = source[1]; - target[2] = source[2]; - } - - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - if (!GetDcmtkObject().getDataset()->insert(pixels.release(), false, false).good()) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - Encoding ParsedDicomFile::DetectEncoding(bool& hasCodeExtensions) const - { - return FromDcmtkBridge::DetectEncoding(hasCodeExtensions, - *GetDcmtkObject().getDataset(), - GetDefaultDicomEncoding()); - } - - - void ParsedDicomFile::SetEncoding(Encoding encoding) - { - if (encoding == Encoding_Windows1251) - { - // This Cyrillic codepage is not officially supported by the - // DICOM standard. Do not set the SpecificCharacterSet tag. - return; - } - - std::string s = GetDicomSpecificCharacterSet(encoding); - ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s); - } - - void ParsedDicomFile::DatasetToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) - { - std::set ignoreTagLength; - FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), - format, flags, maxStringLength, - GetDefaultDicomEncoding(), ignoreTagLength); - } - - - void ParsedDicomFile::DatasetToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - const std::set& ignoreTagLength) - { - FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), - format, flags, maxStringLength, - GetDefaultDicomEncoding(), ignoreTagLength); - } - - - void ParsedDicomFile::DatasetToJson(Json::Value& target, - const std::set& ignoreTagLength) - { - FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), ignoreTagLength); - } - - - void ParsedDicomFile::DatasetToJson(Json::Value& target) - { - const std::set ignoreTagLength; - FromDcmtkBridge::ExtractDicomAsJson(target, *GetDcmtkObject().getDataset(), ignoreTagLength); - } - - - void ParsedDicomFile::HeaderToJson(Json::Value& target, - DicomToJsonFormat format) - { - FromDcmtkBridge::ExtractHeaderAsJson(target, *GetDcmtkObject().getMetaInfo(), format, DicomToJsonFlags_None, 0); - } - - - bool ParsedDicomFile::HasTag(const DicomTag& tag) const - { - DcmTag key(tag.GetGroup(), tag.GetElement()); - return GetDcmtkObject().getDataset()->tagExists(key); - } - - - void ParsedDicomFile::EmbedPdf(const std::string& pdf) - { - if (pdf.size() < 5 || // (*) - strncmp("%PDF-", pdf.c_str(), 5) != 0) - { - throw OrthancException(ErrorCode_BadFileFormat, "Not a PDF file"); - } - - InvalidateCache(); - - ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage); - ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT"); - ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD"); - ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), MIME_PDF); - //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1"); - - std::unique_ptr element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument)); - - size_t s = pdf.size(); - if (s & 1) - { - // The size of the buffer must be even - s += 1; - } - - Uint8* bytes = NULL; - OFCondition result = element->createUint8Array(s, bytes); - if (!result.good() || bytes == NULL) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) ) - bytes[s - 1] = 0; - - memcpy(bytes, pdf.c_str(), pdf.size()); - - DcmPolymorphOBOW* obj = element.release(); - result = GetDcmtkObject().getDataset()->insert(obj); - - if (!result.good()) - { - delete obj; - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - - bool ParsedDicomFile::ExtractPdf(std::string& pdf) - { - std::string sop, mime; - - if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) || - !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) || - sop != UID_EncapsulatedPDFStorage || - mime != MIME_PDF) - { - return false; - } - - if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT)) - { - return false; - } - - // Strip the possible pad byte at the end of file, because the - // encapsulated documents must always have an even length. The PDF - // format expects files to end with %%EOF followed by CR/LF. If - // the last character of the file is not a CR or LF, we assume it - // is a pad byte and remove it. - if (pdf.size() > 0) - { - char last = *pdf.rbegin(); - - if (last != 10 && last != 13) - { - pdf.resize(pdf.size() - 1); - } - } - - return true; - } - - - ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json, - DicomFromJsonFlags flags, - const std::string& privateCreator) - { - const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false; - const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false; - - std::unique_ptr result(new ParsedDicomFile(generateIdentifiers)); - result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding())); - - const Json::Value::Members tags = json.getMemberNames(); - - for (size_t i = 0; i < tags.size(); i++) - { - DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]); - const Json::Value& value = json[tags[i]]; - - if (tag == DICOM_TAG_PIXEL_DATA || - tag == DICOM_TAG_ENCAPSULATED_DOCUMENT) - { - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadRequest); - } - else - { - result->EmbedContent(value.asString()); - } - } - else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET) - { - result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent, privateCreator); - } - } - - return result.release(); - } - - - void ParsedDicomFile::GetRawFrame(std::string& target, - MimeType& mime, - unsigned int frameId) - { - if (pimpl_->frameIndex_.get() == NULL) - { - assert(pimpl_->file_ != NULL && - GetDcmtkObject().getDataset() != NULL); - pimpl_->frameIndex_.reset(new DicomFrameIndex(*GetDcmtkObject().getDataset())); - } - - pimpl_->frameIndex_->GetRawFrame(target, frameId); - - E_TransferSyntax transferSyntax = GetDcmtkObject().getDataset()->getCurrentXfer(); - switch (transferSyntax) - { - case EXS_JPEGProcess1: - mime = MimeType_Jpeg; - break; - - case EXS_JPEG2000LosslessOnly: - case EXS_JPEG2000: - mime = MimeType_Jpeg2000; - break; - - default: - mime = MimeType_Binary; - break; - } - } - - - void ParsedDicomFile::InvalidateCache() - { - pimpl_->frameIndex_.reset(NULL); - } - - - unsigned int ParsedDicomFile::GetFramesCount() const - { - assert(pimpl_->file_ != NULL && - GetDcmtkObject().getDataset() != NULL); - return DicomFrameIndex::GetFramesCount(*GetDcmtkObject().getDataset()); - } - - - void ParsedDicomFile::ChangeEncoding(Encoding target) - { - bool hasCodeExtensions; - Encoding source = DetectEncoding(hasCodeExtensions); - - if (source != target) // Avoid unnecessary conversion - { - ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target)); - FromDcmtkBridge::ChangeStringEncoding(*GetDcmtkObject().getDataset(), source, hasCodeExtensions, target); - } - } - - - void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const - { - FromDcmtkBridge::ExtractDicomSummary(target, *GetDcmtkObject().getDataset()); - } - - - void ParsedDicomFile::ExtractDicomSummary(DicomMap& target, - const std::set& ignoreTagLength) const - { - FromDcmtkBridge::ExtractDicomSummary(target, *GetDcmtkObject().getDataset(), ignoreTagLength); - } - - - bool ParsedDicomFile::LookupTransferSyntax(std::string& result) - { -#if 0 - // This was the implementation in Orthanc <= 1.6.1 - - // TODO - Shouldn't "dataset.getCurrentXfer()" be used instead of - // using the meta header? - const char* value = NULL; - - if (GetDcmtkObject().getMetaInfo() != NULL && - GetDcmtkObject().getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() && - value != NULL) - { - result.assign(value); - return true; - } - else - { - return false; - } -#else - DicomTransferSyntax s; - if (FromDcmtkBridge::LookupOrthancTransferSyntax(s, GetDcmtkObject())) - { - result.assign(GetTransferSyntaxUid(s)); - return true; - } - else - { - return false; - } -#endif - } - - - bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const - { - DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(), - DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement()); - - DcmDataset& dataset = *GetDcmtkObject().getDataset(); - - const char *c = NULL; - if (dataset.findAndGetString(k, c).good() && - c != NULL) - { - result = StringToPhotometricInterpretation(c); - return true; - } - else - { - return false; - } - } - - - void ParsedDicomFile::Apply(ITagVisitor& visitor) - { - FromDcmtkBridge::Apply(*GetDcmtkObject().getDataset(), visitor, GetDefaultDicomEncoding()); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/ParsedDicomFile.h --- a/Core/DicomParsing/ParsedDicomFile.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,270 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../OrthancFramework.h" - -#if !defined(ORTHANC_ENABLE_JPEG) -# error Macro ORTHANC_ENABLE_JPEG must be defined to use this file -#endif - -#if !defined(ORTHANC_ENABLE_PNG) -# error Macro ORTHANC_ENABLE_PNG must be defined to use this file -#endif - -#if !defined(ORTHANC_ENABLE_CIVETWEB) -# error Macro ORTHANC_ENABLE_CIVETWEB must be defined to use this file -#endif - -#if !defined(ORTHANC_ENABLE_MONGOOSE) -# error Macro ORTHANC_ENABLE_MONGOOSE must be defined to use this file -#endif - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK) -# error The macro ORTHANC_ENABLE_DCMTK must be defined -#endif - -#if ORTHANC_ENABLE_DCMTK != 1 -# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 to use this file -#endif - -#include "ITagVisitor.h" -#include "../DicomFormat/DicomInstanceHasher.h" -#include "../Images/ImageAccessor.h" -#include "../IDynamicObject.h" -#include "../Toolbox.h" - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 -# include "../RestApi/RestApiOutput.h" -#endif - -#include - - -class DcmDataset; -class DcmFileFormat; - -namespace Orthanc -{ - class ORTHANC_PUBLIC ParsedDicomFile : public IDynamicObject - { - private: - struct PImpl; - boost::shared_ptr pimpl_; - - ParsedDicomFile(ParsedDicomFile& other, - bool keepSopInstanceUid); - - void CreateFromDicomMap(const DicomMap& source, - Encoding defaultEncoding, - bool permissive, - const std::string& defaultPrivateCreator, - const std::map& privateCreators); - - void RemovePrivateTagsInternal(const std::set* toKeep); - - void UpdateStorageUid(const DicomTag& tag, - const std::string& value, - bool decodeDataUriScheme); - - void InvalidateCache(); - - bool EmbedContentInternal(const std::string& dataUriScheme); - - ParsedDicomFile(DcmFileFormat* dicom); // This takes ownership (no clone) - - public: - ParsedDicomFile(bool createIdentifiers); // Create a minimal DICOM instance - - ParsedDicomFile(const DicomMap& map, - Encoding defaultEncoding, - bool permissive - ); - - ParsedDicomFile(const DicomMap& map, - Encoding defaultEncoding, - bool permissive, - const std::string& defaultPrivateCreator, - const std::map& privateCreators - ); - - ParsedDicomFile(const void* content, - size_t size); - - ParsedDicomFile(const std::string& content); - - ParsedDicomFile(DcmDataset& dicom); // This clones the DCMTK object - - ParsedDicomFile(DcmFileFormat& dicom); // This clones the DCMTK object - - static ParsedDicomFile* AcquireDcmtkObject(DcmFileFormat* dicom) // No clone here - { - return new ParsedDicomFile(dicom); - } - - DcmFileFormat& GetDcmtkObject() const; - - // The "ParsedDicomFile" object cannot be used after calling this method - DcmFileFormat* ReleaseDcmtkObject(); - - ParsedDicomFile* Clone(bool keepSopInstanceUid); - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 - void SendPathValue(RestApiOutput& output, - const UriComponents& uri); - - void Answer(RestApiOutput& output); -#endif - - void Remove(const DicomTag& tag); - - // Replace the DICOM tag as a NULL/empty value (e.g. for anonymization) - void Clear(const DicomTag& tag, - bool onlyIfExists); - - void Replace(const DicomTag& tag, - const std::string& utf8Value, - bool decodeDataUriScheme, - DicomReplaceMode mode, - const std::string& privateCreator /* used only for private tags */); - - void Replace(const DicomTag& tag, - const Json::Value& value, // Assumed to be encoded with UTF-8 - bool decodeDataUriScheme, - DicomReplaceMode mode, - const std::string& privateCreator /* used only for private tags */); - - void Insert(const DicomTag& tag, - const Json::Value& value, // Assumed to be encoded with UTF-8 - bool decodeDataUriScheme, - const std::string& privateCreator /* used only for private tags */); - - // Cannot be applied to private tags - void ReplacePlainString(const DicomTag& tag, - const std::string& utf8Value); - - // Cannot be applied to private tags - void SetIfAbsent(const DicomTag& tag, - const std::string& utf8Value); - - void RemovePrivateTags() - { - RemovePrivateTagsInternal(NULL); - } - - void RemovePrivateTags(const std::set& toKeep) - { - RemovePrivateTagsInternal(&toKeep); - } - - // WARNING: This function handles the decoding of strings to UTF8 - bool GetTagValue(std::string& value, - const DicomTag& tag); - - DicomInstanceHasher GetHasher(); - - void SaveToMemoryBuffer(std::string& buffer); - -#if ORTHANC_SANDBOXED == 0 - void SaveToFile(const std::string& path); -#endif - - void EmbedContent(const std::string& dataUriScheme); - - void EmbedImage(const ImageAccessor& accessor); - - void EmbedImage(MimeType mime, - const std::string& content); - - Encoding DetectEncoding(bool& hasCodeExtensions) const; - - // WARNING: This function only sets the encoding, it will not - // convert the encoding of the tags. Use "ChangeEncoding()" if need be. - void SetEncoding(Encoding encoding); - - void DatasetToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength); - - void DatasetToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength, - const std::set& ignoreTagLength); - - // This version uses the default parameters for - // FileContentType_DicomAsJson - void DatasetToJson(Json::Value& target, - const std::set& ignoreTagLength); - - void DatasetToJson(Json::Value& target); - - void HeaderToJson(Json::Value& target, - DicomToJsonFormat format); - - bool HasTag(const DicomTag& tag) const; - - void EmbedPdf(const std::string& pdf); - - bool ExtractPdf(std::string& pdf); - - void GetRawFrame(std::string& target, // OUT - MimeType& mime, // OUT - unsigned int frameId); // IN - - unsigned int GetFramesCount() const; - - static ParsedDicomFile* CreateFromJson(const Json::Value& value, - DicomFromJsonFlags flags, - const std::string& privateCreator); - - void ChangeEncoding(Encoding target); - - void ExtractDicomSummary(DicomMap& target) const; - - void ExtractDicomSummary(DicomMap& target, - const std::set& ignoreTagLength) const; - - bool LookupTransferSyntax(std::string& result); - - bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const; - - void Apply(ITagVisitor& visitor); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/ToDcmtkBridge.cpp --- a/Core/DicomParsing/ToDcmtkBridge.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "ToDcmtkBridge.h" - -#include - -#include "../OrthancException.h" - - -namespace Orthanc -{ - DcmEVR ToDcmtkBridge::Convert(ValueRepresentation vr) - { - switch (vr) - { - case ValueRepresentation_ApplicationEntity: - return EVR_AE; - - case ValueRepresentation_AgeString: - return EVR_AS; - - case ValueRepresentation_AttributeTag: - return EVR_AT; - - case ValueRepresentation_CodeString: - return EVR_CS; - - case ValueRepresentation_Date: - return EVR_DA; - - case ValueRepresentation_DecimalString: - return EVR_DS; - - case ValueRepresentation_DateTime: - return EVR_DT; - - case ValueRepresentation_FloatingPointSingle: - return EVR_FL; - - case ValueRepresentation_FloatingPointDouble: - return EVR_FD; - - case ValueRepresentation_IntegerString: - return EVR_IS; - - case ValueRepresentation_LongString: - return EVR_LO; - - case ValueRepresentation_LongText: - return EVR_LT; - - case ValueRepresentation_OtherByte: - return EVR_OB; - - // Not supported as of DCMTK 3.6.0 - /*case ValueRepresentation_OtherDouble: - return EVR_OD;*/ - - case ValueRepresentation_OtherFloat: - return EVR_OF; - - // Not supported as of DCMTK 3.6.0 - /*case ValueRepresentation_OtherLong: - return EVR_OL;*/ - - case ValueRepresentation_OtherWord: - return EVR_OW; - - case ValueRepresentation_PersonName: - return EVR_PN; - - case ValueRepresentation_ShortString: - return EVR_SH; - - case ValueRepresentation_SignedLong: - return EVR_SL; - - case ValueRepresentation_Sequence: - return EVR_SQ; - - case ValueRepresentation_SignedShort: - return EVR_SS; - - case ValueRepresentation_ShortText: - return EVR_ST; - - case ValueRepresentation_Time: - return EVR_TM; - - // Not supported as of DCMTK 3.6.0 - /*case ValueRepresentation_UnlimitedCharacters: - return EVR_UC;*/ - - case ValueRepresentation_UniqueIdentifier: - return EVR_UI; - - case ValueRepresentation_UnsignedLong: - return EVR_UL; - - case ValueRepresentation_Unknown: - return EVR_UN; - - // Not supported as of DCMTK 3.6.0 - /*case ValueRepresentation_UniversalResource: - return EVR_UR;*/ - - case ValueRepresentation_UnsignedShort: - return EVR_US; - - case ValueRepresentation_UnlimitedText: - return EVR_UT; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/DicomParsing/ToDcmtkBridge.h --- a/Core/DicomParsing/ToDcmtkBridge.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if ORTHANC_ENABLE_DCMTK != 1 -# error The macro ORTHANC_ENABLE_DCMTK must be set to 1 -#endif - -#include "../DicomFormat/DicomMap.h" -#include - -namespace Orthanc -{ - class ToDcmtkBridge - { - public: - static DcmTagKey Convert(const DicomTag& tag) - { - return DcmTagKey(tag.GetGroup(), tag.GetElement()); - } - - static DcmEVR Convert(ValueRepresentation vr); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Endianness.h --- a/Core/Endianness.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,220 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - - -/******************************************************************** - ** LINUX-LIKE ARCHITECTURES - ********************************************************************/ - -#if defined(__LSB_VERSION__) -// Linux Standard Base (LSB) does not come with be16toh, be32toh, and -// be64toh -# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0 -# include -#elif defined(__linux__) || defined(__EMSCRIPTEN__) -# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 -# include -#endif - - -/******************************************************************** - ** WINDOWS ARCHITECTURES - ** - ** On Windows x86, "host" will always be little-endian ("le"). - ********************************************************************/ - -#if defined(_WIN32) -# if defined(_MSC_VER) -// Visual Studio - http://msdn.microsoft.com/en-us/library/a3140177.aspx -# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 -# define be16toh(x) _byteswap_ushort(x) -# define be32toh(x) _byteswap_ulong(x) -# define be64toh(x) _byteswap_uint64(x) -# elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) -// MinGW >= 4.3 - Use builtin intrinsic for byte swapping -# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 -# define be16toh(x) __builtin_bswap16(x) -# define be32toh(x) __builtin_bswap32(x) -# define be64toh(x) __builtin_bswap64(x) -# else -// MinGW <= 4.2, we must manually implement the byte swapping (*) -# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0 -# define be16toh(x) __orthanc_bswap16(x) -# define be32toh(x) __orthanc_bswap32(x) -# define be64toh(x) __orthanc_bswap64(x) -# endif - -# define htobe16(x) be16toh(x) -# define htobe32(x) be32toh(x) -# define htobe64(x) be64toh(x) - -# define htole16(x) x -# define htole32(x) x -# define htole64(x) x - -# define le16toh(x) x -# define le32toh(x) x -# define le64toh(x) x -#endif - - -/******************************************************************** - ** FREEBSD ARCHITECTURES - ********************************************************************/ - -#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) -# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 -# include -#endif - - -/******************************************************************** - ** OPENBSD ARCHITECTURES - ********************************************************************/ - -#if defined(__OpenBSD__) -# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 -# include -#endif - - -/******************************************************************** - ** APPLE ARCHITECTURES (including OS X) - ********************************************************************/ - -#if defined(__APPLE__) -# define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1 -# include -# define be16toh(x) OSSwapBigToHostInt16(x) -# define be32toh(x) OSSwapBigToHostInt32(x) -# define be64toh(x) OSSwapBigToHostInt64(x) - -# define htobe16(x) OSSwapHostToBigInt16(x) -# define htobe32(x) OSSwapHostToBigInt32(x) -# define htobe64(x) OSSwapHostToBigInt64(x) - -# define htole16(x) OSSwapHostToLittleInt16(x) -# define htole32(x) OSSwapHostToLittleInt32(x) -# define htole64(x) OSSwapHostToLittleInt64(x) - -# define le16toh(x) OSSwapLittleToHostInt16(x) -# define le32toh(x) OSSwapLittleToHostInt32(x) -# define le64toh(x) OSSwapLittleToHostInt64(x) -#endif - - -/******************************************************************** - ** PORTABLE (BUT SLOW) IMPLEMENTATION OF BYTE-SWAPPING - ********************************************************************/ - -#if ORTHANC_HAS_BUILTIN_BYTE_SWAP != 1 - -#include - -static inline uint16_t __orthanc_bswap16(uint16_t a) -{ - /** - * Note that an alternative implementation was included in Orthanc - * 1.4.0 and 1.4.1: - * - * # hg log -p -r 2706 - * - * This alternative implementation only hid an underlying problem - * with pointer alignment on some architectures, and was thus - * reverted. Check out issue #99: - * https://bitbucket.org/sjodogne/orthanc/issues/99 - **/ - return (a << 8) | (a >> 8); -} - -static inline uint32_t __orthanc_bswap32(uint32_t a) -{ - const uint8_t* p = reinterpret_cast(&a); - return (static_cast(p[0]) << 24 | - static_cast(p[1]) << 16 | - static_cast(p[2]) << 8 | - static_cast(p[3])); -} - -static inline uint64_t __orthanc_bswap64(uint64_t a) -{ - const uint8_t* p = reinterpret_cast(&a); - return (static_cast(p[0]) << 56 | - static_cast(p[1]) << 48 | - static_cast(p[2]) << 40 | - static_cast(p[3]) << 32 | - static_cast(p[4]) << 24 | - static_cast(p[5]) << 16 | - static_cast(p[6]) << 8 | - static_cast(p[7])); -} - -#if defined(_WIN32) -// Implemented above (*) -#elif defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN) -# if __BYTE_ORDER == __LITTLE_ENDIAN -# define be16toh(x) __orthanc_bswap16(x) -# define be32toh(x) __orthanc_bswap32(x) -# define be64toh(x) __orthanc_bswap64(x) -# define htobe16(x) __orthanc_bswap16(x) -# define htobe32(x) __orthanc_bswap32(x) -# define htobe64(x) __orthanc_bswap64(x) -# define htole16(x) x -# define htole32(x) x -# define htole64(x) x -# define le16toh(x) x -# define le32toh(x) x -# define le64toh(x) x -# elif __BYTE_ORDER == __BIG_ENDIAN -# define be16toh(x) x -# define be32toh(x) x -# define be64toh(x) x -# define htobe16(x) x -# define htobe32(x) x -# define htobe64(x) x -# define htole16(x) __orthanc_bswap16(x) -# define htole32(x) __orthanc_bswap32(x) -# define htole64(x) __orthanc_bswap64(x) -# define le16toh(x) __orthanc_bswap16(x) -# define le32toh(x) __orthanc_bswap32(x) -# define le64toh(x) __orthanc_bswap64(x) -# else -# error Please support your platform here -# endif -#else -# error Please support your platform here -#endif - -#endif diff -r 6c6239aec462 -r d25f4c0fa160 Core/EnumerationDictionary.h --- a/Core/EnumerationDictionary.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,119 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "OrthancException.h" - -#include "Toolbox.h" -#include -#include -#include - -namespace Orthanc -{ - template - class EnumerationDictionary - { - private: - typedef std::map EnumerationToString; - typedef std::map StringToEnumeration; - - EnumerationToString enumerationToString_; - StringToEnumeration stringToEnumeration_; - - public: - void Clear() - { - enumerationToString_.clear(); - stringToEnumeration_.clear(); - } - - bool Contains(Enumeration value) const - { - return enumerationToString_.find(value) != enumerationToString_.end(); - } - - void Add(Enumeration value, const std::string& str) - { - // Check if these values are free - if (enumerationToString_.find(value) != enumerationToString_.end() || - stringToEnumeration_.find(str) != stringToEnumeration_.end() || - Toolbox::IsInteger(str) /* Prevent the registration of a number */) - { - throw OrthancException(ErrorCode_BadRequest); - } - - // OK, the string is free and is not a number - enumerationToString_[value] = str; - stringToEnumeration_[str] = value; - stringToEnumeration_[boost::lexical_cast(static_cast(value))] = value; - } - - Enumeration Translate(const std::string& str) const - { - if (Toolbox::IsInteger(str)) - { - return static_cast(boost::lexical_cast(str)); - } - - typename StringToEnumeration::const_iterator - found = stringToEnumeration_.find(str); - - if (found == stringToEnumeration_.end()) - { - throw OrthancException(ErrorCode_InexistentItem); - } - else - { - return found->second; - } - } - - std::string Translate(Enumeration e) const - { - typename EnumerationToString::const_iterator - found = enumerationToString_.find(e); - - if (found == enumerationToString_.end()) - { - // No name for this item - return boost::lexical_cast(static_cast(e)); - } - else - { - return found->second; - } - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Enumerations.cpp --- a/Core/Enumerations.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2255 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "Enumerations.h" - -#include "OrthancException.h" -#include "Toolbox.h" -#include "Logging.h" - -#include -#include -#include - -namespace Orthanc -{ - static const char* const MIME_CSS = "text/css"; - static const char* const MIME_DICOM = "application/dicom"; - static const char* const MIME_GIF = "image/gif"; - static const char* const MIME_GZIP = "application/gzip"; - static const char* const MIME_HTML = "text/html"; - static const char* const MIME_JAVASCRIPT = "application/javascript"; - static const char* const MIME_JPEG2000 = "image/jp2"; - static const char* const MIME_NACL = "application/x-nacl"; - static const char* const MIME_PLAIN_TEXT = "text/plain"; - static const char* const MIME_PNACL = "application/x-pnacl"; - static const char* const MIME_SVG = "image/svg+xml"; - static const char* const MIME_WEB_ASSEMBLY = "application/wasm"; - static const char* const MIME_WOFF = "application/x-font-woff"; - static const char* const MIME_WOFF2 = "font/woff2"; - static const char* const MIME_XML_2 = "text/xml"; - static const char* const MIME_ZIP = "application/zip"; - static const char* const MIME_DICOM_WEB_JSON = "application/dicom+json"; - static const char* const MIME_DICOM_WEB_XML = "application/dicom+xml"; - - // This function is autogenerated by the script - // "Resources/GenerateErrorCodes.py" - const char* EnumerationToString(ErrorCode error) - { - switch (error) - { - case ErrorCode_InternalError: - return "Internal error"; - - case ErrorCode_Success: - return "Success"; - - case ErrorCode_Plugin: - return "Error encountered within the plugin engine"; - - case ErrorCode_NotImplemented: - return "Not implemented yet"; - - case ErrorCode_ParameterOutOfRange: - return "Parameter out of range"; - - case ErrorCode_NotEnoughMemory: - return "The server hosting Orthanc is running out of memory"; - - case ErrorCode_BadParameterType: - return "Bad type for a parameter"; - - case ErrorCode_BadSequenceOfCalls: - return "Bad sequence of calls"; - - case ErrorCode_InexistentItem: - return "Accessing an inexistent item"; - - case ErrorCode_BadRequest: - return "Bad request"; - - case ErrorCode_NetworkProtocol: - return "Error in the network protocol"; - - case ErrorCode_SystemCommand: - return "Error while calling a system command"; - - case ErrorCode_Database: - return "Error with the database engine"; - - case ErrorCode_UriSyntax: - return "Badly formatted URI"; - - case ErrorCode_InexistentFile: - return "Inexistent file"; - - case ErrorCode_CannotWriteFile: - return "Cannot write to file"; - - case ErrorCode_BadFileFormat: - return "Bad file format"; - - case ErrorCode_Timeout: - return "Timeout"; - - case ErrorCode_UnknownResource: - return "Unknown resource"; - - case ErrorCode_IncompatibleDatabaseVersion: - return "Incompatible version of the database"; - - case ErrorCode_FullStorage: - return "The file storage is full"; - - case ErrorCode_CorruptedFile: - return "Corrupted file (e.g. inconsistent MD5 hash)"; - - case ErrorCode_InexistentTag: - return "Inexistent tag"; - - case ErrorCode_ReadOnly: - return "Cannot modify a read-only data structure"; - - case ErrorCode_IncompatibleImageFormat: - return "Incompatible format of the images"; - - case ErrorCode_IncompatibleImageSize: - return "Incompatible size of the images"; - - case ErrorCode_SharedLibrary: - return "Error while using a shared library (plugin)"; - - case ErrorCode_UnknownPluginService: - return "Plugin invoking an unknown service"; - - case ErrorCode_UnknownDicomTag: - return "Unknown DICOM tag"; - - case ErrorCode_BadJson: - return "Cannot parse a JSON document"; - - case ErrorCode_Unauthorized: - return "Bad credentials were provided to an HTTP request"; - - case ErrorCode_BadFont: - return "Badly formatted font file"; - - case ErrorCode_DatabasePlugin: - return "The plugin implementing a custom database back-end does not fulfill the proper interface"; - - case ErrorCode_StorageAreaPlugin: - return "Error in the plugin implementing a custom storage area"; - - case ErrorCode_EmptyRequest: - return "The request is empty"; - - case ErrorCode_NotAcceptable: - return "Cannot send a response which is acceptable according to the Accept HTTP header"; - - case ErrorCode_NullPointer: - return "Cannot handle a NULL pointer"; - - case ErrorCode_DatabaseUnavailable: - return "The database is currently not available (probably a transient situation)"; - - case ErrorCode_CanceledJob: - return "This job was canceled"; - - case ErrorCode_BadGeometry: - return "Geometry error encountered in Stone"; - - case ErrorCode_SslInitialization: - return "Cannot initialize SSL encryption, check out your certificates"; - - case ErrorCode_SQLiteNotOpened: - return "SQLite: The database is not opened"; - - case ErrorCode_SQLiteAlreadyOpened: - return "SQLite: Connection is already open"; - - case ErrorCode_SQLiteCannotOpen: - return "SQLite: Unable to open the database"; - - case ErrorCode_SQLiteStatementAlreadyUsed: - return "SQLite: This cached statement is already being referred to"; - - case ErrorCode_SQLiteExecute: - return "SQLite: Cannot execute a command"; - - case ErrorCode_SQLiteRollbackWithoutTransaction: - return "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)"; - - case ErrorCode_SQLiteCommitWithoutTransaction: - return "SQLite: Committing a nonexistent transaction"; - - case ErrorCode_SQLiteRegisterFunction: - return "SQLite: Unable to register a function"; - - case ErrorCode_SQLiteFlush: - return "SQLite: Unable to flush the database"; - - case ErrorCode_SQLiteCannotRun: - return "SQLite: Cannot run a cached statement"; - - case ErrorCode_SQLiteCannotStep: - return "SQLite: Cannot step over a cached statement"; - - case ErrorCode_SQLiteBindOutOfRange: - return "SQLite: Bing a value while out of range (serious error)"; - - case ErrorCode_SQLitePrepareStatement: - return "SQLite: Cannot prepare a cached statement"; - - case ErrorCode_SQLiteTransactionAlreadyStarted: - return "SQLite: Beginning the same transaction twice"; - - case ErrorCode_SQLiteTransactionCommit: - return "SQLite: Failure when committing the transaction"; - - case ErrorCode_SQLiteTransactionBegin: - return "SQLite: Cannot start a transaction"; - - case ErrorCode_DirectoryOverFile: - return "The directory to be created is already occupied by a regular file"; - - case ErrorCode_FileStorageCannotWrite: - return "Unable to create a subdirectory or a file in the file storage"; - - case ErrorCode_DirectoryExpected: - return "The specified path does not point to a directory"; - - case ErrorCode_HttpPortInUse: - return "The TCP port of the HTTP server is privileged or already in use"; - - case ErrorCode_DicomPortInUse: - return "The TCP port of the DICOM server is privileged or already in use"; - - case ErrorCode_BadHttpStatusInRest: - return "This HTTP status is not allowed in a REST API"; - - case ErrorCode_RegularFileExpected: - return "The specified path does not point to a regular file"; - - case ErrorCode_PathToExecutable: - return "Unable to get the path to the executable"; - - case ErrorCode_MakeDirectory: - return "Cannot create a directory"; - - case ErrorCode_BadApplicationEntityTitle: - return "An application entity title (AET) cannot be empty or be longer than 16 characters"; - - case ErrorCode_NoCFindHandler: - return "No request handler factory for DICOM C-FIND SCP"; - - case ErrorCode_NoCMoveHandler: - return "No request handler factory for DICOM C-MOVE SCP"; - - case ErrorCode_NoCStoreHandler: - return "No request handler factory for DICOM C-STORE SCP"; - - case ErrorCode_NoApplicationEntityFilter: - return "No application entity filter"; - - case ErrorCode_NoSopClassOrInstance: - return "DicomUserConnection: Unable to find the SOP class and instance"; - - case ErrorCode_NoPresentationContext: - return "DicomUserConnection: No acceptable presentation context for modality"; - - case ErrorCode_DicomFindUnavailable: - return "DicomUserConnection: The C-FIND command is not supported by the remote SCP"; - - case ErrorCode_DicomMoveUnavailable: - return "DicomUserConnection: The C-MOVE command is not supported by the remote SCP"; - - case ErrorCode_CannotStoreInstance: - return "Cannot store an instance"; - - case ErrorCode_CreateDicomNotString: - return "Only string values are supported when creating DICOM instances"; - - case ErrorCode_CreateDicomOverrideTag: - return "Trying to override a value inherited from a parent module"; - - case ErrorCode_CreateDicomUseContent: - return "Use \"Content\" to inject an image into a new DICOM instance"; - - case ErrorCode_CreateDicomNoPayload: - return "No payload is present for one instance in the series"; - - case ErrorCode_CreateDicomUseDataUriScheme: - return "The payload of the DICOM instance must be specified according to Data URI scheme"; - - case ErrorCode_CreateDicomBadParent: - return "Trying to attach a new DICOM instance to an inexistent resource"; - - case ErrorCode_CreateDicomParentIsInstance: - return "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)"; - - case ErrorCode_CreateDicomParentEncoding: - return "Unable to get the encoding of the parent resource"; - - case ErrorCode_UnknownModality: - return "Unknown modality"; - - case ErrorCode_BadJobOrdering: - return "Bad ordering of filters in a job"; - - case ErrorCode_JsonToLuaTable: - return "Cannot convert the given JSON object to a Lua table"; - - case ErrorCode_CannotCreateLua: - return "Cannot create the Lua context"; - - case ErrorCode_CannotExecuteLua: - return "Cannot execute a Lua command"; - - case ErrorCode_LuaAlreadyExecuted: - return "Arguments cannot be pushed after the Lua function is executed"; - - case ErrorCode_LuaBadOutput: - return "The Lua function does not give the expected number of outputs"; - - case ErrorCode_NotLuaPredicate: - return "The Lua function is not a predicate (only true/false outputs allowed)"; - - case ErrorCode_LuaReturnsNoString: - return "The Lua function does not return a string"; - - case ErrorCode_StorageAreaAlreadyRegistered: - return "Another plugin has already registered a custom storage area"; - - case ErrorCode_DatabaseBackendAlreadyRegistered: - return "Another plugin has already registered a custom database back-end"; - - case ErrorCode_DatabaseNotInitialized: - return "Plugin trying to call the database during its initialization"; - - case ErrorCode_SslDisabled: - return "Orthanc has been built without SSL support"; - - case ErrorCode_CannotOrderSlices: - return "Unable to order the slices of the series"; - - case ErrorCode_NoWorklistHandler: - return "No request handler factory for DICOM C-Find Modality SCP"; - - case ErrorCode_AlreadyExistingTag: - return "Cannot override the value of a tag that already exists"; - - case ErrorCode_NoCGetHandler: - return "No request handler factory for DICOM C-GET SCP"; - - case ErrorCode_NoStorageCommitmentHandler: - return "No request handler factory for DICOM N-ACTION SCP (storage commitment)"; - - case ErrorCode_UnsupportedMediaType: - return "Unsupported media type"; - - default: - if (error >= ErrorCode_START_PLUGINS) - { - return "Error encountered within some plugin"; - } - else - { - return "Unknown error code"; - } - } - } - - - const char* EnumerationToString(HttpMethod method) - { - switch (method) - { - case HttpMethod_Get: - return "GET"; - - case HttpMethod_Post: - return "POST"; - - case HttpMethod_Delete: - return "DELETE"; - - case HttpMethod_Put: - return "PUT"; - - default: - return "?"; - } - } - - - const char* EnumerationToString(HttpStatus status) - { - switch (status) - { - case HttpStatus_100_Continue: - return "Continue"; - - case HttpStatus_101_SwitchingProtocols: - return "Switching Protocols"; - - case HttpStatus_102_Processing: - return "Processing"; - - case HttpStatus_200_Ok: - return "OK"; - - case HttpStatus_201_Created: - return "Created"; - - case HttpStatus_202_Accepted: - return "Accepted"; - - case HttpStatus_203_NonAuthoritativeInformation: - return "Non-Authoritative Information"; - - case HttpStatus_204_NoContent: - return "No Content"; - - case HttpStatus_205_ResetContent: - return "Reset Content"; - - case HttpStatus_206_PartialContent: - return "Partial Content"; - - case HttpStatus_207_MultiStatus: - return "Multi-Status"; - - case HttpStatus_208_AlreadyReported: - return "Already Reported"; - - case HttpStatus_226_IMUsed: - return "IM Used"; - - case HttpStatus_300_MultipleChoices: - return "Multiple Choices"; - - case HttpStatus_301_MovedPermanently: - return "Moved Permanently"; - - case HttpStatus_302_Found: - return "Found"; - - case HttpStatus_303_SeeOther: - return "See Other"; - - case HttpStatus_304_NotModified: - return "Not Modified"; - - case HttpStatus_305_UseProxy: - return "Use Proxy"; - - case HttpStatus_307_TemporaryRedirect: - return "Temporary Redirect"; - - case HttpStatus_400_BadRequest: - return "Bad Request"; - - case HttpStatus_401_Unauthorized: - return "Unauthorized"; - - case HttpStatus_402_PaymentRequired: - return "Payment Required"; - - case HttpStatus_403_Forbidden: - return "Forbidden"; - - case HttpStatus_404_NotFound: - return "Not Found"; - - case HttpStatus_405_MethodNotAllowed: - return "Method Not Allowed"; - - case HttpStatus_406_NotAcceptable: - return "Not Acceptable"; - - case HttpStatus_407_ProxyAuthenticationRequired: - return "Proxy Authentication Required"; - - case HttpStatus_408_RequestTimeout: - return "Request Timeout"; - - case HttpStatus_409_Conflict: - return "Conflict"; - - case HttpStatus_410_Gone: - return "Gone"; - - case HttpStatus_411_LengthRequired: - return "Length Required"; - - case HttpStatus_412_PreconditionFailed: - return "Precondition Failed"; - - case HttpStatus_413_RequestEntityTooLarge: - return "Request Entity Too Large"; - - case HttpStatus_414_RequestUriTooLong: - return "Request-URI Too Long"; - - case HttpStatus_415_UnsupportedMediaType: - return "Unsupported Media Type"; - - case HttpStatus_416_RequestedRangeNotSatisfiable: - return "Requested Range Not Satisfiable"; - - case HttpStatus_417_ExpectationFailed: - return "Expectation Failed"; - - case HttpStatus_422_UnprocessableEntity: - return "Unprocessable Entity"; - - case HttpStatus_423_Locked: - return "Locked"; - - case HttpStatus_424_FailedDependency: - return "Failed Dependency"; - - case HttpStatus_426_UpgradeRequired: - return "Upgrade Required"; - - case HttpStatus_500_InternalServerError: - return "Internal Server Error"; - - case HttpStatus_501_NotImplemented: - return "Not Implemented"; - - case HttpStatus_502_BadGateway: - return "Bad Gateway"; - - case HttpStatus_503_ServiceUnavailable: - return "Service Unavailable"; - - case HttpStatus_504_GatewayTimeout: - return "Gateway Timeout"; - - case HttpStatus_505_HttpVersionNotSupported: - return "HTTP Version Not Supported"; - - case HttpStatus_506_VariantAlsoNegotiates: - return "Variant Also Negotiates"; - - case HttpStatus_507_InsufficientStorage: - return "Insufficient Storage"; - - case HttpStatus_509_BandwidthLimitExceeded: - return "Bandwidth Limit Exceeded"; - - case HttpStatus_510_NotExtended: - return "Not Extended"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(ResourceType type) - { - switch (type) - { - case ResourceType_Patient: - return "Patient"; - - case ResourceType_Study: - return "Study"; - - case ResourceType_Series: - return "Series"; - - case ResourceType_Instance: - return "Instance"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(ImageFormat format) - { - switch (format) - { - case ImageFormat_Png: - return "Png"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(Encoding encoding) - { - switch (encoding) - { - case Encoding_Ascii: - return "Ascii"; - - case Encoding_Utf8: - return "Utf8"; - - case Encoding_Latin1: - return "Latin1"; - - case Encoding_Latin2: - return "Latin2"; - - case Encoding_Latin3: - return "Latin3"; - - case Encoding_Latin4: - return "Latin4"; - - case Encoding_Latin5: - return "Latin5"; - - case Encoding_Cyrillic: - return "Cyrillic"; - - case Encoding_Windows1251: - return "Windows1251"; - - case Encoding_Arabic: - return "Arabic"; - - case Encoding_Greek: - return "Greek"; - - case Encoding_Hebrew: - return "Hebrew"; - - case Encoding_Thai: - return "Thai"; - - case Encoding_Japanese: - return "Japanese"; - - case Encoding_Chinese: - return "Chinese"; - - case Encoding_Korean: - return "Korean"; - - case Encoding_JapaneseKanji: - return "JapaneseKanji"; - - case Encoding_SimplifiedChinese: - return "SimplifiedChinese"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(PhotometricInterpretation photometric) - { - switch (photometric) - { - case PhotometricInterpretation_RGB: - return "RGB"; - - case PhotometricInterpretation_Monochrome1: - return "MONOCHROME1"; - - case PhotometricInterpretation_Monochrome2: - return "MONOCHROME2"; - - case PhotometricInterpretation_ARGB: - return "ARGB"; - - case PhotometricInterpretation_CMYK: - return "CMYK"; - - case PhotometricInterpretation_HSV: - return "HSV"; - - case PhotometricInterpretation_Palette: - return "PALETTE COLOR"; - - case PhotometricInterpretation_YBRFull: - return "YBR_FULL"; - - case PhotometricInterpretation_YBRFull422: - return "YBR_FULL_422"; - - case PhotometricInterpretation_YBRPartial420: - return "YBR_PARTIAL_420"; - - case PhotometricInterpretation_YBRPartial422: - return "YBR_PARTIAL_422"; - - case PhotometricInterpretation_YBR_ICT: - return "YBR_ICT"; - - case PhotometricInterpretation_YBR_RCT: - return "YBR_RCT"; - - case PhotometricInterpretation_Unknown: - return "Unknown"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(RequestOrigin origin) - { - switch (origin) - { - case RequestOrigin_Unknown: - return "Unknown"; - - case RequestOrigin_DicomProtocol: - return "DicomProtocol"; - - case RequestOrigin_RestApi: - return "RestApi"; - - case RequestOrigin_Plugins: - return "Plugins"; - - case RequestOrigin_Lua: - return "Lua"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(PixelFormat format) - { - switch (format) - { - case PixelFormat_RGB24: - return "RGB24"; - - case PixelFormat_RGBA32: - return "RGBA32"; - - case PixelFormat_BGRA32: - return "BGRA32"; - - case PixelFormat_Grayscale8: - return "Grayscale (unsigned 8bpp)"; - - case PixelFormat_Grayscale16: - return "Grayscale (unsigned 16bpp)"; - - case PixelFormat_SignedGrayscale16: - return "Grayscale (signed 16bpp)"; - - case PixelFormat_Float32: - return "Grayscale (float 32bpp)"; - - case PixelFormat_Grayscale32: - return "Grayscale (unsigned 32bpp)"; - - case PixelFormat_Grayscale64: - return "Grayscale (unsigned 64bpp)"; - - case PixelFormat_RGB48: - return "RGB48"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(ModalityManufacturer manufacturer) - { - switch (manufacturer) - { - case ModalityManufacturer_Generic: - return "Generic"; - - case ModalityManufacturer_GenericNoWildcardInDates: - return "GenericNoWildcardInDates"; - - case ModalityManufacturer_GenericNoUniversalWildcard: - return "GenericNoUniversalWildcard"; - - case ModalityManufacturer_StoreScp: - return "StoreScp"; - - case ModalityManufacturer_Vitrea: - return "Vitrea"; - - case ModalityManufacturer_GE: - return "GE"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(DicomRequestType type) - { - switch (type) - { - case DicomRequestType_Echo: - return "Echo"; - break; - - case DicomRequestType_Find: - return "Find"; - break; - - case DicomRequestType_Get: - return "Get"; - break; - - case DicomRequestType_Move: - return "Move"; - break; - - case DicomRequestType_Store: - return "Store"; - break; - - case DicomRequestType_NAction: - return "N-ACTION"; - break; - - case DicomRequestType_NEventReport: - return "N-EVENT-REPORT"; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(TransferSyntax syntax) - { - switch (syntax) - { - case TransferSyntax_Deflated: - return "Deflated"; - - case TransferSyntax_Jpeg: - return "JPEG"; - - case TransferSyntax_Jpeg2000: - return "JPEG2000"; - - case TransferSyntax_JpegLossless: - return "JPEG Lossless"; - - case TransferSyntax_Jpip: - return "JPIP"; - - case TransferSyntax_Mpeg2: - return "MPEG2"; - - case TransferSyntax_Mpeg4: - return "MPEG4"; - - case TransferSyntax_Rle: - return "RLE"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(DicomVersion version) - { - switch (version) - { - case DicomVersion_2008: - return "2008"; - break; - - case DicomVersion_2017c: - return "2017c"; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(ValueRepresentation vr) - { - switch (vr) - { - case ValueRepresentation_ApplicationEntity: // AE - return "AE"; - - case ValueRepresentation_AgeString: // AS - return "AS"; - - case ValueRepresentation_AttributeTag: // AT (2 x uint16_t) - return "AT"; - - case ValueRepresentation_CodeString: // CS - return "CS"; - - case ValueRepresentation_Date: // DA - return "DA"; - - case ValueRepresentation_DecimalString: // DS - return "DS"; - - case ValueRepresentation_DateTime: // DT - return "DT"; - - case ValueRepresentation_FloatingPointSingle: // FL (float) - return "FL"; - - case ValueRepresentation_FloatingPointDouble: // FD (double) - return "FD"; - - case ValueRepresentation_IntegerString: // IS - return "IS"; - - case ValueRepresentation_LongString: // LO - return "LO"; - - case ValueRepresentation_LongText: // LT - return "LT"; - - case ValueRepresentation_OtherByte: // OB - return "OB"; - - case ValueRepresentation_OtherDouble: // OD - return "OD"; - - case ValueRepresentation_OtherFloat: // OF - return "OF"; - - case ValueRepresentation_OtherLong: // OL - return "OL"; - - case ValueRepresentation_OtherWord: // OW - return "OW"; - - case ValueRepresentation_PersonName: // PN - return "PN"; - - case ValueRepresentation_ShortString: // SH - return "SH"; - - case ValueRepresentation_SignedLong: // SL (int32_t) - return "SL"; - - case ValueRepresentation_Sequence: // SQ - return "SQ"; - - case ValueRepresentation_SignedShort: // SS (int16_t) - return "SS"; - - case ValueRepresentation_ShortText: // ST - return "ST"; - - case ValueRepresentation_Time: // TM - return "TM"; - - case ValueRepresentation_UnlimitedCharacters: // UC - return "UC"; - - case ValueRepresentation_UniqueIdentifier: // UI (UID) - return "UI"; - - case ValueRepresentation_UnsignedLong: // UL (uint32_t) - return "UL"; - - case ValueRepresentation_Unknown: // UN - return "UN"; - - case ValueRepresentation_UniversalResource: // UR (URI or URL) - return "UR"; - - case ValueRepresentation_UnsignedShort: // US (uint16_t) - return "US"; - - case ValueRepresentation_UnlimitedText: // UT - return "UT"; - - case ValueRepresentation_NotSupported: - return "Not supported"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(JobState state) - { - switch (state) - { - case JobState_Pending: - return "Pending"; - - case JobState_Running: - return "Running"; - - case JobState_Success: - return "Success"; - - case JobState_Failure: - return "Failure"; - - case JobState_Paused: - return "Paused"; - - case JobState_Retry: - return "Retry"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(MimeType mime) - { - switch (mime) - { - case MimeType_Binary: - return MIME_BINARY; - - case MimeType_Dicom: - return MIME_DICOM; - - case MimeType_Jpeg: - return MIME_JPEG; - - case MimeType_Jpeg2000: - return MIME_JPEG2000; - - case MimeType_Json: - return MIME_JSON; - - case MimeType_Pdf: - return MIME_PDF; - - case MimeType_Png: - return MIME_PNG; - - case MimeType_Xml: - return MIME_XML; - - case MimeType_PlainText: - return MIME_PLAIN_TEXT; - - case MimeType_Pam: - return MIME_PAM; - - case MimeType_Html: - return MIME_HTML; - - case MimeType_Gzip: - return MIME_GZIP; - - case MimeType_JavaScript: - return MIME_JAVASCRIPT; - - case MimeType_Css: - return MIME_CSS; - - case MimeType_WebAssembly: - return MIME_WEB_ASSEMBLY; - - case MimeType_Gif: - return MIME_GIF; - - case MimeType_Zip: - return MIME_ZIP; - - case MimeType_NaCl: - return MIME_NACL; - - case MimeType_PNaCl: - return MIME_PNACL; - - case MimeType_Svg: - return MIME_SVG; - - case MimeType_Woff: - return MIME_WOFF; - - case MimeType_Woff2: - return MIME_WOFF2; - - case MimeType_PrometheusText: - // https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format - return "text/plain; version=0.0.4"; - - case MimeType_DicomWebJson: - return MIME_DICOM_WEB_JSON; - - case MimeType_DicomWebXml: - return MIME_DICOM_WEB_XML; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(Endianness endianness) - { - switch (endianness) - { - case Endianness_Little: - return "Little-endian"; - - case Endianness_Big: - return "Big-endian"; - - case Endianness_Unknown: - return "Unknown endianness"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - const char* EnumerationToString(StorageCommitmentFailureReason reason) - { - switch (reason) - { - case StorageCommitmentFailureReason_Success: - return "Success"; - - case StorageCommitmentFailureReason_ProcessingFailure: - return "A general failure in processing the operation was encountered"; - - case StorageCommitmentFailureReason_NoSuchObjectInstance: - return "One or more of the elements in the Referenced SOP " - "Instance Sequence was not available"; - - case StorageCommitmentFailureReason_ResourceLimitation: - return "The SCP does not currently have enough resources to " - "store the requested SOP Instance(s)"; - - case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported: - return "Storage Commitment has been requested for a SOP Instance " - "with a SOP Class that is not supported by the SCP"; - - case StorageCommitmentFailureReason_ClassInstanceConflict: - return "The SOP Class of an element in the Referenced SOP Instance Sequence " - "did not correspond to the SOP class registered for this SOP Instance at the SCP"; - - case StorageCommitmentFailureReason_DuplicateTransactionUID: - return "The Transaction UID of the Storage Commitment Request is already in use"; - - default: - return "Unknown failure reason"; - } - } - - - Encoding StringToEncoding(const char* encoding) - { - std::string s(encoding); - Toolbox::ToUpperCase(s); - - if (s == "UTF8") - { - return Encoding_Utf8; - } - - if (s == "ASCII") - { - return Encoding_Ascii; - } - - if (s == "LATIN1") - { - return Encoding_Latin1; - } - - if (s == "LATIN2") - { - return Encoding_Latin2; - } - - if (s == "LATIN3") - { - return Encoding_Latin3; - } - - if (s == "LATIN4") - { - return Encoding_Latin4; - } - - if (s == "LATIN5") - { - return Encoding_Latin5; - } - - if (s == "CYRILLIC") - { - return Encoding_Cyrillic; - } - - if (s == "WINDOWS1251") - { - return Encoding_Windows1251; - } - - if (s == "ARABIC") - { - return Encoding_Arabic; - } - - if (s == "GREEK") - { - return Encoding_Greek; - } - - if (s == "HEBREW") - { - return Encoding_Hebrew; - } - - if (s == "THAI") - { - return Encoding_Thai; - } - - if (s == "JAPANESE") - { - return Encoding_Japanese; - } - - if (s == "CHINESE") - { - return Encoding_Chinese; - } - - if (s == "KOREAN") - { - return Encoding_Korean; - } - - if (s == "JAPANESEKANJI") - { - return Encoding_JapaneseKanji; - } - - if (s == "SIMPLIFIEDCHINESE") - { - return Encoding_SimplifiedChinese; - } - - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - ResourceType StringToResourceType(const char* type) - { - std::string s(type); - Toolbox::ToUpperCase(s); - - if (s == "PATIENT" || s == "PATIENTS") - { - return ResourceType_Patient; - } - else if (s == "STUDY" || s == "STUDIES") - { - return ResourceType_Study; - } - else if (s == "SERIES") - { - return ResourceType_Series; - } - else if (s == "INSTANCE" || s == "IMAGE" || - s == "INSTANCES" || s == "IMAGES") - { - return ResourceType_Instance; - } - - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - ImageFormat StringToImageFormat(const char* format) - { - std::string s(format); - Toolbox::ToUpperCase(s); - - if (s == "PNG") - { - return ImageFormat_Png; - } - - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - ValueRepresentation StringToValueRepresentation(const std::string& vr, - bool throwIfUnsupported) - { - if (vr == "AE") - { - return ValueRepresentation_ApplicationEntity; - } - else if (vr == "AS") - { - return ValueRepresentation_AgeString; - } - else if (vr == "AT") - { - return ValueRepresentation_AttributeTag; - } - else if (vr == "CS") - { - return ValueRepresentation_CodeString; - } - else if (vr == "DA") - { - return ValueRepresentation_Date; - } - else if (vr == "DS") - { - return ValueRepresentation_DecimalString; - } - else if (vr == "DT") - { - return ValueRepresentation_DateTime; - } - else if (vr == "FL") - { - return ValueRepresentation_FloatingPointSingle; - } - else if (vr == "FD") - { - return ValueRepresentation_FloatingPointDouble; - } - else if (vr == "IS") - { - return ValueRepresentation_IntegerString; - } - else if (vr == "LO") - { - return ValueRepresentation_LongString; - } - else if (vr == "LT") - { - return ValueRepresentation_LongText; - } - else if (vr == "OB") - { - return ValueRepresentation_OtherByte; - } - else if (vr == "OD") - { - return ValueRepresentation_OtherDouble; - } - else if (vr == "OF") - { - return ValueRepresentation_OtherFloat; - } - else if (vr == "OL") - { - return ValueRepresentation_OtherLong; - } - else if (vr == "OW") - { - return ValueRepresentation_OtherWord; - } - else if (vr == "PN") - { - return ValueRepresentation_PersonName; - } - else if (vr == "SH") - { - return ValueRepresentation_ShortString; - } - else if (vr == "SL") - { - return ValueRepresentation_SignedLong; - } - else if (vr == "SQ") - { - return ValueRepresentation_Sequence; - } - else if (vr == "SS") - { - return ValueRepresentation_SignedShort; - } - else if (vr == "ST") - { - return ValueRepresentation_ShortText; - } - else if (vr == "TM") - { - return ValueRepresentation_Time; - } - else if (vr == "UC") - { - return ValueRepresentation_UnlimitedCharacters; - } - else if (vr == "UI") - { - return ValueRepresentation_UniqueIdentifier; - } - else if (vr == "UL") - { - return ValueRepresentation_UnsignedLong; - } - else if (vr == "UN") - { - return ValueRepresentation_Unknown; - } - else if (vr == "UR") - { - return ValueRepresentation_UniversalResource; - } - else if (vr == "US") - { - return ValueRepresentation_UnsignedShort; - } - else if (vr == "UT") - { - return ValueRepresentation_UnlimitedText; - } - else - { - std::string s = "Unsupported value representation encountered: " + vr; - - if (throwIfUnsupported) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, s); - } - else - { - LOG(INFO) << s; - return ValueRepresentation_NotSupported; - } - } - } - - - PhotometricInterpretation StringToPhotometricInterpretation(const char* value) - { - // http://dicom.nema.org/medical/dicom/2017a/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2 - std::string s(value); - - if (s == "MONOCHROME1") - { - return PhotometricInterpretation_Monochrome1; - } - - if (s == "MONOCHROME2") - { - return PhotometricInterpretation_Monochrome2; - } - - if (s == "PALETTE COLOR") - { - return PhotometricInterpretation_Palette; - } - - if (s == "RGB") - { - return PhotometricInterpretation_RGB; - } - - if (s == "HSV") - { - return PhotometricInterpretation_HSV; - } - - if (s == "ARGB") - { - return PhotometricInterpretation_ARGB; - } - - if (s == "CMYK") - { - return PhotometricInterpretation_CMYK; - } - - if (s == "YBR_FULL") - { - return PhotometricInterpretation_YBRFull; - } - - if (s == "YBR_FULL_422") - { - return PhotometricInterpretation_YBRFull422; - } - - if (s == "YBR_PARTIAL_422") - { - return PhotometricInterpretation_YBRPartial422; - } - - if (s == "YBR_PARTIAL_420") - { - return PhotometricInterpretation_YBRPartial420; - } - - if (s == "YBR_ICT") - { - return PhotometricInterpretation_YBR_ICT; - } - - if (s == "YBR_RCT") - { - return PhotometricInterpretation_YBR_RCT; - } - - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - - ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer) - { - ModalityManufacturer result; - bool obsolete = false; - - if (manufacturer == "Generic") - { - return ModalityManufacturer_Generic; - } - else if (manufacturer == "GenericNoWildcardInDates") - { - return ModalityManufacturer_GenericNoWildcardInDates; - } - else if (manufacturer == "GenericNoUniversalWildcard") - { - return ModalityManufacturer_GenericNoUniversalWildcard; - } - else if (manufacturer == "StoreScp") - { - return ModalityManufacturer_StoreScp; - } - else if (manufacturer == "Vitrea") - { - return ModalityManufacturer_Vitrea; - } - else if (manufacturer == "GE") - { - return ModalityManufacturer_GE; - } - else if (manufacturer == "AgfaImpax" || - manufacturer == "SyngoVia") - { - result = ModalityManufacturer_GenericNoWildcardInDates; - obsolete = true; - } - else if (manufacturer == "EFilm2" || - manufacturer == "MedInria" || - manufacturer == "ClearCanvas" || - manufacturer == "Dcm4Chee" - ) - { - result = ModalityManufacturer_Generic; - obsolete = true; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Unknown modality manufacturer: \"" + manufacturer + "\""); - } - - if (obsolete) - { - LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is now obsolete. " - << "To guarantee compatibility with future Orthanc " - << "releases, you should replace it by \"" - << EnumerationToString(result) - << "\" in your configuration file."; - } - - return result; - } - - - DicomVersion StringToDicomVersion(const std::string& version) - { - if (version == "2008") - { - return DicomVersion_2008; - } - else if (version == "2017c") - { - return DicomVersion_2017c; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - JobState StringToJobState(const std::string& state) - { - if (state == "Pending") - { - return JobState_Pending; - } - else if (state == "Running") - { - return JobState_Running; - } - else if (state == "Success") - { - return JobState_Success; - } - else if (state == "Failure") - { - return JobState_Failure; - } - else if (state == "Paused") - { - return JobState_Paused; - } - else if (state == "Retry") - { - return JobState_Retry; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - RequestOrigin StringToRequestOrigin(const std::string& origin) - { - if (origin == "Unknown") - { - return RequestOrigin_Unknown; - } - else if (origin == "DicomProtocol") - { - return RequestOrigin_DicomProtocol; - } - else if (origin == "RestApi") - { - return RequestOrigin_RestApi; - } - else if (origin == "Plugins") - { - return RequestOrigin_Plugins; - } - else if (origin == "Lua") - { - return RequestOrigin_Lua; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - MimeType StringToMimeType(const std::string& mime) - { - if (mime == MIME_BINARY) - { - return MimeType_Binary; - } - else if (mime == MIME_DICOM) - { - return MimeType_Dicom; - } - else if (mime == MIME_JPEG) - { - return MimeType_Jpeg; - } - else if (mime == MIME_JPEG2000) - { - return MimeType_Jpeg2000; - } - else if (mime == MIME_JSON) - { - return MimeType_Json; - } - else if (mime == MIME_PDF) - { - return MimeType_Pdf; - } - else if (mime == MIME_PNG) - { - return MimeType_Png; - } - else if (mime == MIME_XML || - mime == MIME_XML_2) - { - return MimeType_Xml; - } - else if (mime == MIME_PLAIN_TEXT) - { - return MimeType_PlainText; - } - else if (mime == MIME_PAM) - { - return MimeType_Pam; - } - else if (mime == MIME_HTML) - { - return MimeType_Html; - } - else if (mime == MIME_GZIP) - { - return MimeType_Gzip; - } - else if (mime == MIME_JAVASCRIPT) - { - return MimeType_JavaScript; - } - else if (mime == MIME_CSS) - { - return MimeType_Css; - } - else if (mime == MIME_WEB_ASSEMBLY) - { - return MimeType_WebAssembly; - } - else if (mime == MIME_GIF) - { - return MimeType_Gif; - } - else if (mime == MIME_ZIP) - { - return MimeType_Zip; - } - else if (mime == MIME_NACL) - { - return MimeType_NaCl; - } - else if (mime == MIME_PNACL) - { - return MimeType_PNaCl; - } - else if (mime == MIME_SVG) - { - return MimeType_Svg; - } - else if (mime == MIME_WOFF) - { - return MimeType_Woff; - } - else if (mime == MIME_WOFF2) - { - return MimeType_Woff2; - } - else if (mime == MIME_DICOM_WEB_JSON) - { - return MimeType_DicomWebJson; - } - else if (mime == MIME_DICOM_WEB_XML) - { - return MimeType_DicomWebXml; - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - unsigned int GetBytesPerPixel(PixelFormat format) - { - switch (format) - { - case PixelFormat_Grayscale8: - return 1; - - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - return 2; - - case PixelFormat_RGB24: - return 3; - - case PixelFormat_RGBA32: - case PixelFormat_BGRA32: - case PixelFormat_Grayscale32: - return 4; - - case PixelFormat_Float32: - assert(sizeof(float) == 4); - return 4; - - case PixelFormat_RGB48: - return 6; - - case PixelFormat_Grayscale64: - return 8; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - bool GetDicomEncoding(Encoding& encoding, - const char* specificCharacterSet) - { - std::string s = Toolbox::StripSpaces(specificCharacterSet); - Toolbox::ToUpperCase(s); - - // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2 - // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java - if (s == "ISO_IR 6" || - s == "ISO 2022 IR 6") - { - encoding = Encoding_Ascii; - } - else if (s == "ISO_IR 192") - { - encoding = Encoding_Utf8; - } - else if (s == "ISO_IR 100" || - s == "ISO 2022 IR 100") - { - encoding = Encoding_Latin1; - } - else if (s == "ISO_IR 101" || - s == "ISO 2022 IR 101") - { - encoding = Encoding_Latin2; - } - else if (s == "ISO_IR 109" || - s == "ISO 2022 IR 109") - { - encoding = Encoding_Latin3; - } - else if (s == "ISO_IR 110" || - s == "ISO 2022 IR 110") - { - encoding = Encoding_Latin4; - } - else if (s == "ISO_IR 148" || - s == "ISO 2022 IR 148") - { - encoding = Encoding_Latin5; - } - else if (s == "ISO_IR 144" || - s == "ISO 2022 IR 144") - { - encoding = Encoding_Cyrillic; - } - else if (s == "ISO_IR 127" || - s == "ISO 2022 IR 127") - { - encoding = Encoding_Arabic; - } - else if (s == "ISO_IR 126" || - s == "ISO 2022 IR 126") - { - encoding = Encoding_Greek; - } - else if (s == "ISO_IR 138" || - s == "ISO 2022 IR 138") - { - encoding = Encoding_Hebrew; - } - else if (s == "ISO_IR 166" || - s == "ISO 2022 IR 166") - { - encoding = Encoding_Thai; - } - else if (s == "ISO_IR 13" || - s == "ISO 2022 IR 13") - { - encoding = Encoding_Japanese; - } - else if (s == "GB18030" || s == "GBK") - { - /** - * According to tumashu@163.com, "In China, many dicom file's - * 0008,0005 tag is set as "GBK", instead of "GB18030", GBK is a - * subset of GB18030, and which is used frequently in China, - * suggest support it." - * https://groups.google.com/d/msg/orthanc-users/WMM8LMbjpUc/02-1f_yFCgAJ - **/ - encoding = Encoding_Chinese; - } - else if (s == "ISO 2022 IR 149") - { - encoding = Encoding_Korean; - } - else if (s == "ISO 2022 IR 87") - { - encoding = Encoding_JapaneseKanji; - } - else if (s == "ISO 2022 IR 58") - { - encoding = Encoding_SimplifiedChinese; - } - /* - else if (s == "ISO 2022 IR 159") - { - TODO - Supplementary Kanji set - } - */ - else - { - return false; - } - - // The encoding was properly detected - return true; - } - - - ResourceType GetChildResourceType(ResourceType type) - { - switch (type) - { - case ResourceType_Patient: - return ResourceType_Study; - - case ResourceType_Study: - return ResourceType_Series; - - case ResourceType_Series: - return ResourceType_Instance; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - ResourceType GetParentResourceType(ResourceType type) - { - switch (type) - { - case ResourceType_Study: - return ResourceType_Patient; - - case ResourceType_Series: - return ResourceType_Study; - - case ResourceType_Instance: - return ResourceType_Series; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - bool IsResourceLevelAboveOrEqual(ResourceType level, - ResourceType reference) - { - switch (reference) - { - case ResourceType_Patient: - return (level == ResourceType_Patient); - - case ResourceType_Study: - return (level == ResourceType_Patient || - level == ResourceType_Study); - - case ResourceType_Series: - return (level == ResourceType_Patient || - level == ResourceType_Study || - level == ResourceType_Series); - - case ResourceType_Instance: - return (level == ResourceType_Patient || - level == ResourceType_Study || - level == ResourceType_Series || - level == ResourceType_Instance); - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - DicomModule GetModule(ResourceType type) - { - switch (type) - { - case ResourceType_Patient: - return DicomModule_Patient; - - case ResourceType_Study: - return DicomModule_Study; - - case ResourceType_Series: - return DicomModule_Series; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - - const char* GetDicomSpecificCharacterSet(Encoding encoding) - { - // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2 - switch (encoding) - { - case Encoding_Ascii: - return "ISO_IR 6"; - - case Encoding_Utf8: - return "ISO_IR 192"; - - case Encoding_Latin1: - return "ISO_IR 100"; - - case Encoding_Latin2: - return "ISO_IR 101"; - - case Encoding_Latin3: - return "ISO_IR 109"; - - case Encoding_Latin4: - return "ISO_IR 110"; - - case Encoding_Latin5: - return "ISO_IR 148"; - - case Encoding_Cyrillic: - return "ISO_IR 144"; - - case Encoding_Arabic: - return "ISO_IR 127"; - - case Encoding_Greek: - return "ISO_IR 126"; - - case Encoding_Hebrew: - return "ISO_IR 138"; - - case Encoding_Japanese: - return "ISO_IR 13"; - - case Encoding_Chinese: - return "GB18030"; - - case Encoding_Thai: - return "ISO_IR 166"; - - case Encoding_Korean: - return "ISO 2022 IR 149"; - - case Encoding_JapaneseKanji: - return "ISO 2022 IR 87"; - - case Encoding_SimplifiedChinese: - return "ISO 2022 IR 58"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - // This function is autogenerated by the script - // "Resources/GenerateErrorCodes.py" - HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error) - { - switch (error) - { - case ErrorCode_Success: - return HttpStatus_200_Ok; - - case ErrorCode_ParameterOutOfRange: - return HttpStatus_400_BadRequest; - - case ErrorCode_BadParameterType: - return HttpStatus_400_BadRequest; - - case ErrorCode_InexistentItem: - return HttpStatus_404_NotFound; - - case ErrorCode_BadRequest: - return HttpStatus_400_BadRequest; - - case ErrorCode_UriSyntax: - return HttpStatus_400_BadRequest; - - case ErrorCode_InexistentFile: - return HttpStatus_404_NotFound; - - case ErrorCode_BadFileFormat: - return HttpStatus_400_BadRequest; - - case ErrorCode_UnknownResource: - return HttpStatus_404_NotFound; - - case ErrorCode_InexistentTag: - return HttpStatus_404_NotFound; - - case ErrorCode_BadJson: - return HttpStatus_400_BadRequest; - - case ErrorCode_Unauthorized: - return HttpStatus_401_Unauthorized; - - case ErrorCode_NotAcceptable: - return HttpStatus_406_NotAcceptable; - - case ErrorCode_DatabaseUnavailable: - return HttpStatus_503_ServiceUnavailable; - - case ErrorCode_CreateDicomNotString: - return HttpStatus_400_BadRequest; - - case ErrorCode_CreateDicomOverrideTag: - return HttpStatus_400_BadRequest; - - case ErrorCode_CreateDicomUseContent: - return HttpStatus_400_BadRequest; - - case ErrorCode_CreateDicomNoPayload: - return HttpStatus_400_BadRequest; - - case ErrorCode_CreateDicomUseDataUriScheme: - return HttpStatus_400_BadRequest; - - case ErrorCode_CreateDicomBadParent: - return HttpStatus_400_BadRequest; - - case ErrorCode_CreateDicomParentIsInstance: - return HttpStatus_400_BadRequest; - - case ErrorCode_UnsupportedMediaType: - return HttpStatus_415_UnsupportedMediaType; - - default: - return HttpStatus_500_InternalServerError; - } - } - - - bool IsUserContentType(FileContentType type) - { - return (type >= FileContentType_StartUser && - type <= FileContentType_EndUser); - } - - - bool IsBinaryValueRepresentation(ValueRepresentation vr) - { - // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html - - switch (vr) - { - case ValueRepresentation_ApplicationEntity: // AE - case ValueRepresentation_AgeString: // AS - case ValueRepresentation_CodeString: // CS - case ValueRepresentation_Date: // DA - case ValueRepresentation_DecimalString: // DS - case ValueRepresentation_DateTime: // DT - case ValueRepresentation_IntegerString: // IS - case ValueRepresentation_LongString: // LO - case ValueRepresentation_LongText: // LT - case ValueRepresentation_PersonName: // PN - case ValueRepresentation_ShortString: // SH - case ValueRepresentation_ShortText: // ST - case ValueRepresentation_Time: // TM - case ValueRepresentation_UnlimitedCharacters: // UC - case ValueRepresentation_UniqueIdentifier: // UI (UID) - case ValueRepresentation_UniversalResource: // UR (URI or URL) - case ValueRepresentation_UnlimitedText: // UT - { - return false; - } - - /** - * Below are all the VR whose character repertoire is tagged as - * "not applicable" - **/ - case ValueRepresentation_AttributeTag: // AT (2 x uint16_t) - case ValueRepresentation_FloatingPointSingle: // FL (float) - case ValueRepresentation_FloatingPointDouble: // FD (double) - case ValueRepresentation_OtherByte: // OB - case ValueRepresentation_OtherDouble: // OD - case ValueRepresentation_OtherFloat: // OF - case ValueRepresentation_OtherLong: // OL - case ValueRepresentation_OtherWord: // OW - case ValueRepresentation_SignedLong: // SL (int32_t) - case ValueRepresentation_Sequence: // SQ - case ValueRepresentation_SignedShort: // SS (int16_t) - case ValueRepresentation_UnsignedLong: // UL (uint32_t) - case ValueRepresentation_Unknown: // UN - case ValueRepresentation_UnsignedShort: // US (uint16_t) - { - return true; - } - - case ValueRepresentation_NotSupported: - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - static boost::mutex defaultEncodingMutex_; // Should not be necessary - static Encoding defaultEncoding_ = ORTHANC_DEFAULT_DICOM_ENCODING; - - Encoding GetDefaultDicomEncoding() - { - boost::mutex::scoped_lock lock(defaultEncodingMutex_); - return defaultEncoding_; - } - - void SetDefaultDicomEncoding(Encoding encoding) - { - std::string name = EnumerationToString(encoding); - - { - boost::mutex::scoped_lock lock(defaultEncodingMutex_); - defaultEncoding_ = encoding; - } - - LOG(INFO) << "Default encoding for DICOM was changed to: " << name; - } -} - - -#include "./Enumerations_TransferSyntaxes.impl.h" diff -r 6c6239aec462 -r d25f4c0fa160 Core/Enumerations.h --- a/Core/Enumerations.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,934 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "OrthancFramework.h" - -#include - - -// Macro "ORTHANC_FORCE_INLINE" forces a function/method to be inlined -#if defined(_MSC_VER) -# define ORTHANC_FORCE_INLINE __forceinline -#elif defined(__GNUC__) || defined(__clang__) || defined(__EMSCRIPTEN__) -# define ORTHANC_FORCE_INLINE inline __attribute((always_inline)) -#else -# error Please support your compiler here -#endif - - -// Macros "ORTHANC_OVERRIDE" and "ORTHANC_FINAL" wrap the "override" -// and "final" keywords introduced in C++11, to do compile-time -// checking of virtual methods -// The __cplusplus macro is broken in Visual Studio up to 15.6 and, in -// later versions, require the usage of the /Zc:__cplusplus flag -// We thus use an alternate way of checking for 'override' support -#ifdef ORTHANC_OVERRIDE_SUPPORTED -#error ORTHANC_OVERRIDE_SUPPORTED cannot be defined at this point -#endif - -#if __cplusplus >= 201103L -# define ORTHANC_OVERRIDE_SUPPORTED 1 -#else -# ifdef _MSC_VER -# if _MSC_VER >= 1600 -# define ORTHANC_OVERRIDE_SUPPORTED 1 -# endif -# endif -#endif - -#if ORTHANC_OVERRIDE_SUPPORTED -// The override keyword (C++11) is enabled -# define ORTHANC_OVERRIDE override -# define ORTHANC_FINAL final -#else -// The override keyword (C++11) is not available -# define ORTHANC_OVERRIDE -# define ORTHANC_FINAL -#endif - -namespace Orthanc -{ - static const char* const URI_SCHEME_PREFIX_BINARY = "data:application/octet-stream;base64,"; - - static const char* const MIME_BINARY = "application/octet-stream"; - static const char* const MIME_JPEG = "image/jpeg"; - static const char* const MIME_JSON = "application/json"; - static const char* const MIME_JSON_UTF8 = "application/json; charset=utf-8"; - static const char* const MIME_PDF = "application/pdf"; - static const char* const MIME_PNG = "image/png"; - static const char* const MIME_XML = "application/xml"; - static const char* const MIME_XML_UTF8 = "application/xml; charset=utf-8"; - - /** - * "No Internet Media Type (aka MIME type, content type) for PBM has - * been registered with IANA, but the unofficial value - * image/x-portable-arbitrarymap is assigned by this specification, - * to be consistent with conventional values for the older Netpbm - * formats." http://netpbm.sourceforge.net/doc/pam.html - **/ - static const char* const MIME_PAM = "image/x-portable-arbitrarymap"; - - - enum MimeType - { - MimeType_Binary, - MimeType_Css, - MimeType_Dicom, - MimeType_Gif, - MimeType_Gzip, - MimeType_Html, - MimeType_JavaScript, - MimeType_Jpeg, - MimeType_Jpeg2000, - MimeType_Json, - MimeType_NaCl, - MimeType_PNaCl, - MimeType_Pam, - MimeType_Pdf, - MimeType_PlainText, - MimeType_Png, - MimeType_Svg, - MimeType_WebAssembly, - MimeType_Xml, - MimeType_Woff, // Web Open Font Format - MimeType_Woff2, - MimeType_Zip, - MimeType_PrometheusText, // Prometheus text-based exposition format (for metrics) - MimeType_DicomWebJson, - MimeType_DicomWebXml - }; - - - enum Endianness - { - Endianness_Unknown, - Endianness_Big, - Endianness_Little - }; - - // This enumeration is autogenerated by the script - // "Resources/GenerateErrorCodes.py" - enum ErrorCode - { - ErrorCode_InternalError = -1 /*!< Internal error */, - ErrorCode_Success = 0 /*!< Success */, - ErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, - ErrorCode_NotImplemented = 2 /*!< Not implemented yet */, - ErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, - ErrorCode_NotEnoughMemory = 4 /*!< The server hosting Orthanc is running out of memory */, - ErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, - ErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, - ErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, - ErrorCode_BadRequest = 8 /*!< Bad request */, - ErrorCode_NetworkProtocol = 9 /*!< Error in the network protocol */, - ErrorCode_SystemCommand = 10 /*!< Error while calling a system command */, - ErrorCode_Database = 11 /*!< Error with the database engine */, - ErrorCode_UriSyntax = 12 /*!< Badly formatted URI */, - ErrorCode_InexistentFile = 13 /*!< Inexistent file */, - ErrorCode_CannotWriteFile = 14 /*!< Cannot write to file */, - ErrorCode_BadFileFormat = 15 /*!< Bad file format */, - ErrorCode_Timeout = 16 /*!< Timeout */, - ErrorCode_UnknownResource = 17 /*!< Unknown resource */, - ErrorCode_IncompatibleDatabaseVersion = 18 /*!< Incompatible version of the database */, - ErrorCode_FullStorage = 19 /*!< The file storage is full */, - ErrorCode_CorruptedFile = 20 /*!< Corrupted file (e.g. inconsistent MD5 hash) */, - ErrorCode_InexistentTag = 21 /*!< Inexistent tag */, - ErrorCode_ReadOnly = 22 /*!< Cannot modify a read-only data structure */, - ErrorCode_IncompatibleImageFormat = 23 /*!< Incompatible format of the images */, - ErrorCode_IncompatibleImageSize = 24 /*!< Incompatible size of the images */, - ErrorCode_SharedLibrary = 25 /*!< Error while using a shared library (plugin) */, - ErrorCode_UnknownPluginService = 26 /*!< Plugin invoking an unknown service */, - ErrorCode_UnknownDicomTag = 27 /*!< Unknown DICOM tag */, - ErrorCode_BadJson = 28 /*!< Cannot parse a JSON document */, - ErrorCode_Unauthorized = 29 /*!< Bad credentials were provided to an HTTP request */, - ErrorCode_BadFont = 30 /*!< Badly formatted font file */, - ErrorCode_DatabasePlugin = 31 /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */, - ErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, - ErrorCode_EmptyRequest = 33 /*!< The request is empty */, - ErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, - ErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, - ErrorCode_DatabaseUnavailable = 36 /*!< The database is currently not available (probably a transient situation) */, - ErrorCode_CanceledJob = 37 /*!< This job was canceled */, - ErrorCode_BadGeometry = 38 /*!< Geometry error encountered in Stone */, - ErrorCode_SslInitialization = 39 /*!< Cannot initialize SSL encryption, check out your certificates */, - ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, - ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, - ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, - ErrorCode_SQLiteStatementAlreadyUsed = 1003 /*!< SQLite: This cached statement is already being referred to */, - ErrorCode_SQLiteExecute = 1004 /*!< SQLite: Cannot execute a command */, - ErrorCode_SQLiteRollbackWithoutTransaction = 1005 /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */, - ErrorCode_SQLiteCommitWithoutTransaction = 1006 /*!< SQLite: Committing a nonexistent transaction */, - ErrorCode_SQLiteRegisterFunction = 1007 /*!< SQLite: Unable to register a function */, - ErrorCode_SQLiteFlush = 1008 /*!< SQLite: Unable to flush the database */, - ErrorCode_SQLiteCannotRun = 1009 /*!< SQLite: Cannot run a cached statement */, - ErrorCode_SQLiteCannotStep = 1010 /*!< SQLite: Cannot step over a cached statement */, - ErrorCode_SQLiteBindOutOfRange = 1011 /*!< SQLite: Bing a value while out of range (serious error) */, - ErrorCode_SQLitePrepareStatement = 1012 /*!< SQLite: Cannot prepare a cached statement */, - ErrorCode_SQLiteTransactionAlreadyStarted = 1013 /*!< SQLite: Beginning the same transaction twice */, - ErrorCode_SQLiteTransactionCommit = 1014 /*!< SQLite: Failure when committing the transaction */, - ErrorCode_SQLiteTransactionBegin = 1015 /*!< SQLite: Cannot start a transaction */, - ErrorCode_DirectoryOverFile = 2000 /*!< The directory to be created is already occupied by a regular file */, - ErrorCode_FileStorageCannotWrite = 2001 /*!< Unable to create a subdirectory or a file in the file storage */, - ErrorCode_DirectoryExpected = 2002 /*!< The specified path does not point to a directory */, - ErrorCode_HttpPortInUse = 2003 /*!< The TCP port of the HTTP server is privileged or already in use */, - ErrorCode_DicomPortInUse = 2004 /*!< The TCP port of the DICOM server is privileged or already in use */, - ErrorCode_BadHttpStatusInRest = 2005 /*!< This HTTP status is not allowed in a REST API */, - ErrorCode_RegularFileExpected = 2006 /*!< The specified path does not point to a regular file */, - ErrorCode_PathToExecutable = 2007 /*!< Unable to get the path to the executable */, - ErrorCode_MakeDirectory = 2008 /*!< Cannot create a directory */, - ErrorCode_BadApplicationEntityTitle = 2009 /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */, - ErrorCode_NoCFindHandler = 2010 /*!< No request handler factory for DICOM C-FIND SCP */, - ErrorCode_NoCMoveHandler = 2011 /*!< No request handler factory for DICOM C-MOVE SCP */, - ErrorCode_NoCStoreHandler = 2012 /*!< No request handler factory for DICOM C-STORE SCP */, - ErrorCode_NoApplicationEntityFilter = 2013 /*!< No application entity filter */, - ErrorCode_NoSopClassOrInstance = 2014 /*!< DicomUserConnection: Unable to find the SOP class and instance */, - ErrorCode_NoPresentationContext = 2015 /*!< DicomUserConnection: No acceptable presentation context for modality */, - ErrorCode_DicomFindUnavailable = 2016 /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */, - ErrorCode_DicomMoveUnavailable = 2017 /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */, - ErrorCode_CannotStoreInstance = 2018 /*!< Cannot store an instance */, - ErrorCode_CreateDicomNotString = 2019 /*!< Only string values are supported when creating DICOM instances */, - ErrorCode_CreateDicomOverrideTag = 2020 /*!< Trying to override a value inherited from a parent module */, - ErrorCode_CreateDicomUseContent = 2021 /*!< Use \"Content\" to inject an image into a new DICOM instance */, - ErrorCode_CreateDicomNoPayload = 2022 /*!< No payload is present for one instance in the series */, - ErrorCode_CreateDicomUseDataUriScheme = 2023 /*!< The payload of the DICOM instance must be specified according to Data URI scheme */, - ErrorCode_CreateDicomBadParent = 2024 /*!< Trying to attach a new DICOM instance to an inexistent resource */, - ErrorCode_CreateDicomParentIsInstance = 2025 /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */, - ErrorCode_CreateDicomParentEncoding = 2026 /*!< Unable to get the encoding of the parent resource */, - ErrorCode_UnknownModality = 2027 /*!< Unknown modality */, - ErrorCode_BadJobOrdering = 2028 /*!< Bad ordering of filters in a job */, - ErrorCode_JsonToLuaTable = 2029 /*!< Cannot convert the given JSON object to a Lua table */, - ErrorCode_CannotCreateLua = 2030 /*!< Cannot create the Lua context */, - ErrorCode_CannotExecuteLua = 2031 /*!< Cannot execute a Lua command */, - ErrorCode_LuaAlreadyExecuted = 2032 /*!< Arguments cannot be pushed after the Lua function is executed */, - ErrorCode_LuaBadOutput = 2033 /*!< The Lua function does not give the expected number of outputs */, - ErrorCode_NotLuaPredicate = 2034 /*!< The Lua function is not a predicate (only true/false outputs allowed) */, - ErrorCode_LuaReturnsNoString = 2035 /*!< The Lua function does not return a string */, - ErrorCode_StorageAreaAlreadyRegistered = 2036 /*!< Another plugin has already registered a custom storage area */, - ErrorCode_DatabaseBackendAlreadyRegistered = 2037 /*!< Another plugin has already registered a custom database back-end */, - ErrorCode_DatabaseNotInitialized = 2038 /*!< Plugin trying to call the database during its initialization */, - ErrorCode_SslDisabled = 2039 /*!< Orthanc has been built without SSL support */, - ErrorCode_CannotOrderSlices = 2040 /*!< Unable to order the slices of the series */, - ErrorCode_NoWorklistHandler = 2041 /*!< No request handler factory for DICOM C-Find Modality SCP */, - ErrorCode_AlreadyExistingTag = 2042 /*!< Cannot override the value of a tag that already exists */, - ErrorCode_NoStorageCommitmentHandler = 2043 /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */, - ErrorCode_NoCGetHandler = 2044 /*!< No request handler factory for DICOM C-GET SCP */, - ErrorCode_UnsupportedMediaType = 3000 /*!< Unsupported media type */, - ErrorCode_START_PLUGINS = 1000000 - }; - - // This enumeration is autogenerated by the script - // "Resources/GenerateTransferSyntaxes.py" - enum DicomTransferSyntax - { - DicomTransferSyntax_LittleEndianImplicit /*!< Implicit VR Little Endian */, - DicomTransferSyntax_LittleEndianExplicit /*!< Explicit VR Little Endian */, - DicomTransferSyntax_DeflatedLittleEndianExplicit /*!< Deflated Explicit VR Little Endian */, - DicomTransferSyntax_BigEndianExplicit /*!< Explicit VR Big Endian */, - DicomTransferSyntax_JPEGProcess1 /*!< JPEG Baseline (process 1, lossy) */, - DicomTransferSyntax_JPEGProcess2_4 /*!< JPEG Extended Sequential (processes 2 & 4) */, - DicomTransferSyntax_JPEGProcess3_5 /*!< JPEG Extended Sequential (lossy, 8/12 bit), arithmetic coding */, - DicomTransferSyntax_JPEGProcess6_8 /*!< JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit) */, - DicomTransferSyntax_JPEGProcess7_9 /*!< JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit), arithmetic coding */, - DicomTransferSyntax_JPEGProcess10_12 /*!< JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit) */, - DicomTransferSyntax_JPEGProcess11_13 /*!< JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit), arithmetic coding */, - DicomTransferSyntax_JPEGProcess14 /*!< JPEG Lossless, Nonhierarchical with any selection value (process 14) */, - DicomTransferSyntax_JPEGProcess15 /*!< JPEG Lossless with any selection value, arithmetic coding */, - DicomTransferSyntax_JPEGProcess16_18 /*!< JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit) */, - DicomTransferSyntax_JPEGProcess17_19 /*!< JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit), arithmetic coding */, - DicomTransferSyntax_JPEGProcess20_22 /*!< JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit) */, - DicomTransferSyntax_JPEGProcess21_23 /*!< JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit), arithmetic coding */, - DicomTransferSyntax_JPEGProcess24_26 /*!< JPEG Full Progression, Hierarchical (lossy, 8/12 bit) */, - DicomTransferSyntax_JPEGProcess25_27 /*!< JPEG Full Progression, Hierarchical (lossy, 8/12 bit), arithmetic coding */, - DicomTransferSyntax_JPEGProcess28 /*!< JPEG Lossless, Hierarchical */, - DicomTransferSyntax_JPEGProcess29 /*!< JPEG Lossless, Hierarchical, arithmetic coding */, - DicomTransferSyntax_JPEGProcess14SV1 /*!< JPEG Lossless, Nonhierarchical, First-Order Prediction (Processes 14 [Selection Value 1]) */, - DicomTransferSyntax_JPEGLSLossless /*!< JPEG-LS (lossless) */, - DicomTransferSyntax_JPEGLSLossy /*!< JPEG-LS (lossy or near-lossless) */, - DicomTransferSyntax_JPEG2000LosslessOnly /*!< JPEG 2000 (lossless) */, - DicomTransferSyntax_JPEG2000 /*!< JPEG 2000 (lossless or lossy) */, - DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly /*!< JPEG 2000 part 2 multicomponent extensions (lossless) */, - DicomTransferSyntax_JPEG2000Multicomponent /*!< JPEG 2000 part 2 multicomponent extensions (lossless or lossy) */, - DicomTransferSyntax_JPIPReferenced /*!< JPIP Referenced */, - DicomTransferSyntax_JPIPReferencedDeflate /*!< JPIP Referenced Deflate */, - DicomTransferSyntax_MPEG2MainProfileAtMainLevel /*!< MPEG2 Main Profile / Main Level */, - DicomTransferSyntax_MPEG2MainProfileAtHighLevel /*!< MPEG2 Main Profile / High Level */, - DicomTransferSyntax_MPEG4HighProfileLevel4_1 /*!< MPEG4 AVC/H.264 High Profile / Level 4.1 */, - DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1 /*!< MPEG4 AVC/H.264 BD-compatible High Profile / Level 4.1 */, - DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo /*!< MPEG4 AVC/H.264 High Profile / Level 4.2 For 2D Video */, - DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo /*!< MPEG4 AVC/H.264 High Profile / Level 4.2 For 3D Video */, - DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2 /*!< MPEG4 AVC/H.264 Stereo High Profile / Level 4.2 */, - DicomTransferSyntax_HEVCMainProfileLevel5_1 /*!< HEVC/H.265 Main Profile / Level 5.1 */, - DicomTransferSyntax_HEVCMain10ProfileLevel5_1 /*!< HEVC/H.265 Main 10 Profile / Level 5.1 */, - DicomTransferSyntax_RLELossless /*!< RLE - Run Length Encoding (lossless) */, - DicomTransferSyntax_RFC2557MimeEncapsulation /*!< RFC 2557 MIME Encapsulation */, - DicomTransferSyntax_XML /*!< XML Encoding */ - }; - - - /** - * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.} - **/ - enum PixelFormat - { - /** - * {summary}{Color image in RGB24 format.} - * {description}{This format describes a color image. The pixels are stored in 3 - * consecutive bytes. The memory layout is RGB.} - **/ - PixelFormat_RGB24 = 1, - - /** - * {summary}{Color image in RGBA32 format.} - * {description}{This format describes a color image. The pixels are stored in 4 - * consecutive bytes. The memory layout is RGBA.} - **/ - PixelFormat_RGBA32 = 2, - - /** - * {summary}{Graylevel 8bpp image.} - * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.} - **/ - PixelFormat_Grayscale8 = 3, - - /** - * {summary}{Graylevel, unsigned 16bpp image.} - * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.} - **/ - PixelFormat_Grayscale16 = 4, - - /** - * {summary}{Graylevel, signed 16bpp image.} - * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.} - **/ - PixelFormat_SignedGrayscale16 = 5, - - /** - * {summary}{Graylevel, floating-point image.} - * {description}{The image is graylevel. Each pixel is floating-point and stored in 4 bytes.} - **/ - PixelFormat_Float32 = 6, - - // This is the memory layout for Cairo (for internal use in Stone of Orthanc) - PixelFormat_BGRA32 = 7, - - /** - * {summary}{Graylevel, unsigned 32bpp image.} - * {description}{The image is graylevel. Each pixel is unsigned and stored in 4 bytes.} - **/ - PixelFormat_Grayscale32 = 8, - - /** - * {summary}{Color image in RGB48 format.} - * {description}{This format describes a color image. The pixels are stored in 6 - * consecutive bytes. The memory layout is RGB.} - **/ - PixelFormat_RGB48 = 9, - - /** - * {summary}{Graylevel, unsigned 64bpp image.} - * {description}{The image is graylevel. Each pixel is unsigned and stored in 8 bytes.} - **/ - PixelFormat_Grayscale64 = 10 - }; - - - /** - * {summary}{The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image.} - **/ - enum ImageExtractionMode - { - /** - * {summary}{Rescaled to 8bpp.} - * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.} - **/ - ImageExtractionMode_Preview = 1, - - /** - * {summary}{Truncation to the [0, 255] range.} - **/ - ImageExtractionMode_UInt8 = 2, - - /** - * {summary}{Truncation to the [0, 65535] range.} - **/ - ImageExtractionMode_UInt16 = 3, - - /** - * {summary}{Truncation to the [-32768, 32767] range.} - **/ - ImageExtractionMode_Int16 = 4 - }; - - - /** - * Most common, non-joke and non-experimental HTTP status codes - * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes - **/ - enum HttpStatus - { - HttpStatus_None = -1, - - // 1xx Informational - HttpStatus_100_Continue = 100, - HttpStatus_101_SwitchingProtocols = 101, - HttpStatus_102_Processing = 102, - - // 2xx Success - HttpStatus_200_Ok = 200, - HttpStatus_201_Created = 201, - HttpStatus_202_Accepted = 202, - HttpStatus_203_NonAuthoritativeInformation = 203, - HttpStatus_204_NoContent = 204, - HttpStatus_205_ResetContent = 205, - HttpStatus_206_PartialContent = 206, - HttpStatus_207_MultiStatus = 207, - HttpStatus_208_AlreadyReported = 208, - HttpStatus_226_IMUsed = 226, - - // 3xx Redirection - HttpStatus_300_MultipleChoices = 300, - HttpStatus_301_MovedPermanently = 301, - HttpStatus_302_Found = 302, - HttpStatus_303_SeeOther = 303, - HttpStatus_304_NotModified = 304, - HttpStatus_305_UseProxy = 305, - HttpStatus_307_TemporaryRedirect = 307, - - // 4xx Client Error - HttpStatus_400_BadRequest = 400, - HttpStatus_401_Unauthorized = 401, - HttpStatus_402_PaymentRequired = 402, - HttpStatus_403_Forbidden = 403, - HttpStatus_404_NotFound = 404, - HttpStatus_405_MethodNotAllowed = 405, - HttpStatus_406_NotAcceptable = 406, - HttpStatus_407_ProxyAuthenticationRequired = 407, - HttpStatus_408_RequestTimeout = 408, - HttpStatus_409_Conflict = 409, - HttpStatus_410_Gone = 410, - HttpStatus_411_LengthRequired = 411, - HttpStatus_412_PreconditionFailed = 412, - HttpStatus_413_RequestEntityTooLarge = 413, - HttpStatus_414_RequestUriTooLong = 414, - HttpStatus_415_UnsupportedMediaType = 415, - HttpStatus_416_RequestedRangeNotSatisfiable = 416, - HttpStatus_417_ExpectationFailed = 417, - HttpStatus_422_UnprocessableEntity = 422, - HttpStatus_423_Locked = 423, - HttpStatus_424_FailedDependency = 424, - HttpStatus_426_UpgradeRequired = 426, - - // 5xx Server Error - HttpStatus_500_InternalServerError = 500, - HttpStatus_501_NotImplemented = 501, - HttpStatus_502_BadGateway = 502, - HttpStatus_503_ServiceUnavailable = 503, - HttpStatus_504_GatewayTimeout = 504, - HttpStatus_505_HttpVersionNotSupported = 505, - HttpStatus_506_VariantAlsoNegotiates = 506, - HttpStatus_507_InsufficientStorage = 507, - HttpStatus_509_BandwidthLimitExceeded = 509, - HttpStatus_510_NotExtended = 510 - }; - - - enum HttpMethod - { - HttpMethod_Get = 0, - HttpMethod_Post = 1, - HttpMethod_Delete = 2, - HttpMethod_Put = 3 - }; - - - enum ImageFormat - { - ImageFormat_Png = 1 - }; - - - // https://en.wikipedia.org/wiki/HTTP_compression - enum HttpCompression - { - HttpCompression_None, - HttpCompression_Deflate, - HttpCompression_Gzip - }; - - - // Specific Character Sets - // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2 - enum Encoding - { - Encoding_Ascii, - Encoding_Utf8, - Encoding_Latin1, - Encoding_Latin2, - Encoding_Latin3, - Encoding_Latin4, - Encoding_Latin5, // Turkish - Encoding_Cyrillic, - Encoding_Windows1251, // Windows-1251 (commonly used for Cyrillic) - Encoding_Arabic, - Encoding_Greek, - Encoding_Hebrew, - Encoding_Thai, // TIS 620-2533 - Encoding_Japanese, // JIS X 0201 (Shift JIS): Katakana - Encoding_Chinese, // GB18030 - Chinese simplified - Encoding_JapaneseKanji, // Multibyte - JIS X 0208: Kanji - //Encoding_JapaneseSupplementaryKanji, // Multibyte - JIS X 0212: Supplementary Kanji set - Encoding_Korean, // Multibyte - KS X 1001: Hangul and Hanja - Encoding_SimplifiedChinese // ISO 2022 IR 58 - }; - - - // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.2 - enum PhotometricInterpretation - { - PhotometricInterpretation_ARGB, // Retired - PhotometricInterpretation_CMYK, // Retired - PhotometricInterpretation_HSV, // Retired - PhotometricInterpretation_Monochrome1, - PhotometricInterpretation_Monochrome2, - PhotometricInterpretation_Palette, - PhotometricInterpretation_RGB, - PhotometricInterpretation_YBRFull, - PhotometricInterpretation_YBRFull422, - PhotometricInterpretation_YBRPartial420, - PhotometricInterpretation_YBRPartial422, - PhotometricInterpretation_YBR_ICT, - PhotometricInterpretation_YBR_RCT, - PhotometricInterpretation_Unknown - }; - - enum DicomModule - { - DicomModule_Patient, - DicomModule_Study, - DicomModule_Series, - DicomModule_Instance, - DicomModule_Image - }; - - enum RequestOrigin - { - RequestOrigin_Unknown, - RequestOrigin_DicomProtocol, - RequestOrigin_RestApi, - RequestOrigin_Plugins, - RequestOrigin_Lua - }; - - enum ServerBarrierEvent - { - ServerBarrierEvent_Stop, - ServerBarrierEvent_Reload // SIGHUP signal: reload configuration file - }; - - enum FileMode - { - FileMode_ReadBinary, - FileMode_WriteBinary - }; - - /** - * The value representations Orthanc knows about. They correspond to - * the DICOM 2016b version of the standard. - * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html - **/ - enum ValueRepresentation - { - ValueRepresentation_ApplicationEntity = 1, // AE - ValueRepresentation_AgeString = 2, // AS - ValueRepresentation_AttributeTag = 3, // AT (2 x uint16_t) - ValueRepresentation_CodeString = 4, // CS - ValueRepresentation_Date = 5, // DA - ValueRepresentation_DecimalString = 6, // DS - ValueRepresentation_DateTime = 7, // DT - ValueRepresentation_FloatingPointSingle = 8, // FL (float) - ValueRepresentation_FloatingPointDouble = 9, // FD (double) - ValueRepresentation_IntegerString = 10, // IS - ValueRepresentation_LongString = 11, // LO - ValueRepresentation_LongText = 12, // LT - ValueRepresentation_OtherByte = 13, // OB - ValueRepresentation_OtherDouble = 14, // OD - ValueRepresentation_OtherFloat = 15, // OF - ValueRepresentation_OtherLong = 16, // OL - ValueRepresentation_OtherWord = 17, // OW - ValueRepresentation_PersonName = 18, // PN - ValueRepresentation_ShortString = 19, // SH - ValueRepresentation_SignedLong = 20, // SL (int32_t) - ValueRepresentation_Sequence = 21, // SQ - ValueRepresentation_SignedShort = 22, // SS (int16_t) - ValueRepresentation_ShortText = 23, // ST - ValueRepresentation_Time = 24, // TM - ValueRepresentation_UnlimitedCharacters = 25, // UC - ValueRepresentation_UniqueIdentifier = 26, // UI (UID) - ValueRepresentation_UnsignedLong = 27, // UL (uint32_t) - ValueRepresentation_Unknown = 28, // UN - ValueRepresentation_UniversalResource = 29, // UR (URI or URL) - ValueRepresentation_UnsignedShort = 30, // US (uint16_t) - ValueRepresentation_UnlimitedText = 31, // UT - ValueRepresentation_NotSupported // Not supported by Orthanc, or tag not in dictionary - }; - - enum DicomReplaceMode - { - DicomReplaceMode_InsertIfAbsent, - DicomReplaceMode_ThrowIfAbsent, - DicomReplaceMode_IgnoreIfAbsent - }; - - enum DicomToJsonFormat - { - DicomToJsonFormat_Full, - DicomToJsonFormat_Short, - DicomToJsonFormat_Human - }; - - enum DicomToJsonFlags - { - DicomToJsonFlags_IncludeBinary = (1 << 0), - DicomToJsonFlags_IncludePrivateTags = (1 << 1), - DicomToJsonFlags_IncludeUnknownTags = (1 << 2), - DicomToJsonFlags_IncludePixelData = (1 << 3), - DicomToJsonFlags_ConvertBinaryToAscii = (1 << 4), - DicomToJsonFlags_ConvertBinaryToNull = (1 << 5), - - // Some predefined combinations - DicomToJsonFlags_None = 0, - DicomToJsonFlags_Default = (DicomToJsonFlags_IncludeBinary | - DicomToJsonFlags_IncludePixelData | - DicomToJsonFlags_IncludePrivateTags | - DicomToJsonFlags_IncludeUnknownTags | - DicomToJsonFlags_ConvertBinaryToNull) - }; - - enum DicomFromJsonFlags - { - DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0), - DicomFromJsonFlags_GenerateIdentifiers = (1 << 1), - - // Some predefined combinations - DicomFromJsonFlags_None = 0 - }; - - enum DicomVersion - { - DicomVersion_2008, - DicomVersion_2017c - }; - - enum ModalityManufacturer - { - ModalityManufacturer_Generic, - ModalityManufacturer_GenericNoWildcardInDates, - ModalityManufacturer_GenericNoUniversalWildcard, - ModalityManufacturer_StoreScp, - ModalityManufacturer_Vitrea, - ModalityManufacturer_GE - }; - - enum DicomRequestType - { - DicomRequestType_Echo, - DicomRequestType_Find, - DicomRequestType_Get, - DicomRequestType_Move, - DicomRequestType_Store, - DicomRequestType_NAction, - DicomRequestType_NEventReport - }; - - enum TransferSyntax - { - TransferSyntax_Deflated, - TransferSyntax_Jpeg, - TransferSyntax_Jpeg2000, - TransferSyntax_JpegLossless, - TransferSyntax_Jpip, - TransferSyntax_Mpeg2, - TransferSyntax_Mpeg4, // New in Orthanc 1.6.0 - TransferSyntax_Rle - }; - - enum JobState - { - JobState_Pending, - JobState_Running, - JobState_Success, - JobState_Failure, - JobState_Paused, - JobState_Retry - }; - - enum JobStepCode - { - JobStepCode_Success, - JobStepCode_Failure, - JobStepCode_Continue, - JobStepCode_Retry - }; - - enum JobStopReason - { - JobStopReason_Paused, - JobStopReason_Canceled, - JobStopReason_Success, - JobStopReason_Failure, - JobStopReason_Retry - }; - - - // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.14.html#sect_C.14.1.1 - enum StorageCommitmentFailureReason - { - StorageCommitmentFailureReason_Success = 0, - - // A general failure in processing the operation was encountered - StorageCommitmentFailureReason_ProcessingFailure = 0x0110, - - // One or more of the elements in the Referenced SOP Instance - // Sequence was not available - StorageCommitmentFailureReason_NoSuchObjectInstance = 0x0112, - - // The SCP does not currently have enough resources to store the - // requested SOP Instance(s) - StorageCommitmentFailureReason_ResourceLimitation = 0x0213, - - // Storage Commitment has been requested for a SOP Instance with a - // SOP Class that is not supported by the SCP - StorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 0x0122, - - // The SOP Class of an element in the Referenced SOP Instance - // Sequence did not correspond to the SOP class registered for - // this SOP Instance at the SCP - StorageCommitmentFailureReason_ClassInstanceConflict = 0x0119, - - // The Transaction UID of the Storage Commitment Request is already in use - StorageCommitmentFailureReason_DuplicateTransactionUID = 0x0131 - }; - - - enum DicomAssociationRole - { - DicomAssociationRole_Default, - DicomAssociationRole_Scu, - DicomAssociationRole_Scp - }; - - - /** - * WARNING: Do not change the explicit values in the enumerations - * below this point. This would result in incompatible databases - * between versions of Orthanc! - **/ - - enum CompressionType - { - /** - * Buffer/file that is stored as-is, in a raw fashion, without - * compression. - **/ - CompressionType_None = 1, - - /** - * Buffer that is compressed using the "deflate" algorithm (RFC - * 1951), wrapped inside the zlib data format (RFC 1950), prefixed - * with a "uint64_t" (8 bytes) that encodes the size of the - * uncompressed buffer. If the compressed buffer is empty, its - * represents an empty uncompressed buffer. This format is - * internal to Orthanc. If the 8 first bytes are skipped AND the - * buffer is non-empty, the buffer is compatible with the - * "deflate" HTTP compression. - **/ - CompressionType_ZlibWithSize = 2 - }; - - enum FileContentType - { - // If you add a value below, insert it in "PluginStorageArea" in - // the file "Plugins/Engine/OrthancPlugins.cpp" - FileContentType_Unknown = 0, - FileContentType_Dicom = 1, - FileContentType_DicomAsJson = 2, - - // Make sure that the value "65535" can be stored into this enumeration - FileContentType_StartUser = 1024, - FileContentType_EndUser = 65535 - }; - - enum ResourceType - { - ResourceType_Patient = 1, - ResourceType_Study = 2, - ResourceType_Series = 3, - ResourceType_Instance = 4 - }; - - - ORTHANC_PUBLIC - const char* EnumerationToString(ErrorCode code); - - ORTHANC_PUBLIC - const char* EnumerationToString(HttpMethod method); - - ORTHANC_PUBLIC - const char* EnumerationToString(HttpStatus status); - - ORTHANC_PUBLIC - const char* EnumerationToString(ResourceType type); - - ORTHANC_PUBLIC - const char* EnumerationToString(ImageFormat format); - - ORTHANC_PUBLIC - const char* EnumerationToString(Encoding encoding); - - ORTHANC_PUBLIC - const char* EnumerationToString(PhotometricInterpretation photometric); - - ORTHANC_PUBLIC - const char* EnumerationToString(RequestOrigin origin); - - ORTHANC_PUBLIC - const char* EnumerationToString(PixelFormat format); - - ORTHANC_PUBLIC - const char* EnumerationToString(ModalityManufacturer manufacturer); - - ORTHANC_PUBLIC - const char* EnumerationToString(DicomRequestType type); - - ORTHANC_PUBLIC - const char* EnumerationToString(TransferSyntax syntax); - - ORTHANC_PUBLIC - const char* EnumerationToString(DicomVersion version); - - ORTHANC_PUBLIC - const char* EnumerationToString(ValueRepresentation vr); - - ORTHANC_PUBLIC - const char* EnumerationToString(JobState state); - - ORTHANC_PUBLIC - const char* EnumerationToString(MimeType mime); - - ORTHANC_PUBLIC - const char* EnumerationToString(Endianness endianness); - - ORTHANC_PUBLIC - const char* EnumerationToString(StorageCommitmentFailureReason reason); - - ORTHANC_PUBLIC - Encoding StringToEncoding(const char* encoding); - - ORTHANC_PUBLIC - ResourceType StringToResourceType(const char* type); - - ORTHANC_PUBLIC - ImageFormat StringToImageFormat(const char* format); - - ORTHANC_PUBLIC - ValueRepresentation StringToValueRepresentation(const std::string& vr, - bool throwIfUnsupported); - - ORTHANC_PUBLIC - PhotometricInterpretation StringToPhotometricInterpretation(const char* value); - - ORTHANC_PUBLIC - ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); - - ORTHANC_PUBLIC - DicomVersion StringToDicomVersion(const std::string& version); - - ORTHANC_PUBLIC - JobState StringToJobState(const std::string& state); - - ORTHANC_PUBLIC - RequestOrigin StringToRequestOrigin(const std::string& origin); - - ORTHANC_PUBLIC - MimeType StringToMimeType(const std::string& mime); - - ORTHANC_PUBLIC - unsigned int GetBytesPerPixel(PixelFormat format); - - ORTHANC_PUBLIC - bool GetDicomEncoding(Encoding& encoding, - const char* specificCharacterSet); - - ORTHANC_PUBLIC - ResourceType GetChildResourceType(ResourceType type); - - ORTHANC_PUBLIC - ResourceType GetParentResourceType(ResourceType type); - - ORTHANC_PUBLIC - bool IsResourceLevelAboveOrEqual(ResourceType level, - ResourceType reference); - - ORTHANC_PUBLIC - DicomModule GetModule(ResourceType type); - - ORTHANC_PUBLIC - const char* GetDicomSpecificCharacterSet(Encoding encoding); - - ORTHANC_PUBLIC - HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error); - - ORTHANC_PUBLIC - bool IsUserContentType(FileContentType type); - - ORTHANC_PUBLIC - bool IsBinaryValueRepresentation(ValueRepresentation vr); - - ORTHANC_PUBLIC - Encoding GetDefaultDicomEncoding(); - - ORTHANC_PUBLIC - void SetDefaultDicomEncoding(Encoding encoding); - - ORTHANC_PUBLIC - const char* GetTransferSyntaxUid(DicomTransferSyntax syntax); - - ORTHANC_PUBLIC - bool IsRetiredTransferSyntax(DicomTransferSyntax syntax); - - ORTHANC_PUBLIC - bool LookupTransferSyntax(DicomTransferSyntax& target, - const std::string& uid); -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Enumerations_TransferSyntaxes.impl.h --- a/Core/Enumerations_TransferSyntaxes.impl.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,566 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - -// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py" - -namespace Orthanc -{ - const char* GetTransferSyntaxUid(DicomTransferSyntax syntax) - { - switch (syntax) - { - case DicomTransferSyntax_LittleEndianImplicit: - return "1.2.840.10008.1.2"; - - case DicomTransferSyntax_LittleEndianExplicit: - return "1.2.840.10008.1.2.1"; - - case DicomTransferSyntax_DeflatedLittleEndianExplicit: - return "1.2.840.10008.1.2.1.99"; - - case DicomTransferSyntax_BigEndianExplicit: - return "1.2.840.10008.1.2.2"; - - case DicomTransferSyntax_JPEGProcess1: - return "1.2.840.10008.1.2.4.50"; - - case DicomTransferSyntax_JPEGProcess2_4: - return "1.2.840.10008.1.2.4.51"; - - case DicomTransferSyntax_JPEGProcess3_5: - return "1.2.840.10008.1.2.4.52"; - - case DicomTransferSyntax_JPEGProcess6_8: - return "1.2.840.10008.1.2.4.53"; - - case DicomTransferSyntax_JPEGProcess7_9: - return "1.2.840.10008.1.2.4.54"; - - case DicomTransferSyntax_JPEGProcess10_12: - return "1.2.840.10008.1.2.4.55"; - - case DicomTransferSyntax_JPEGProcess11_13: - return "1.2.840.10008.1.2.4.56"; - - case DicomTransferSyntax_JPEGProcess14: - return "1.2.840.10008.1.2.4.57"; - - case DicomTransferSyntax_JPEGProcess15: - return "1.2.840.10008.1.2.4.58"; - - case DicomTransferSyntax_JPEGProcess16_18: - return "1.2.840.10008.1.2.4.59"; - - case DicomTransferSyntax_JPEGProcess17_19: - return "1.2.840.10008.1.2.4.60"; - - case DicomTransferSyntax_JPEGProcess20_22: - return "1.2.840.10008.1.2.4.61"; - - case DicomTransferSyntax_JPEGProcess21_23: - return "1.2.840.10008.1.2.4.62"; - - case DicomTransferSyntax_JPEGProcess24_26: - return "1.2.840.10008.1.2.4.63"; - - case DicomTransferSyntax_JPEGProcess25_27: - return "1.2.840.10008.1.2.4.64"; - - case DicomTransferSyntax_JPEGProcess28: - return "1.2.840.10008.1.2.4.65"; - - case DicomTransferSyntax_JPEGProcess29: - return "1.2.840.10008.1.2.4.66"; - - case DicomTransferSyntax_JPEGProcess14SV1: - return "1.2.840.10008.1.2.4.70"; - - case DicomTransferSyntax_JPEGLSLossless: - return "1.2.840.10008.1.2.4.80"; - - case DicomTransferSyntax_JPEGLSLossy: - return "1.2.840.10008.1.2.4.81"; - - case DicomTransferSyntax_JPEG2000LosslessOnly: - return "1.2.840.10008.1.2.4.90"; - - case DicomTransferSyntax_JPEG2000: - return "1.2.840.10008.1.2.4.91"; - - case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly: - return "1.2.840.10008.1.2.4.92"; - - case DicomTransferSyntax_JPEG2000Multicomponent: - return "1.2.840.10008.1.2.4.93"; - - case DicomTransferSyntax_JPIPReferenced: - return "1.2.840.10008.1.2.4.94"; - - case DicomTransferSyntax_JPIPReferencedDeflate: - return "1.2.840.10008.1.2.4.95"; - - case DicomTransferSyntax_MPEG2MainProfileAtMainLevel: - return "1.2.840.10008.1.2.4.100"; - - case DicomTransferSyntax_MPEG2MainProfileAtHighLevel: - return "1.2.840.10008.1.2.4.101"; - - case DicomTransferSyntax_MPEG4HighProfileLevel4_1: - return "1.2.840.10008.1.2.4.102"; - - case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1: - return "1.2.840.10008.1.2.4.103"; - - case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo: - return "1.2.840.10008.1.2.4.104"; - - case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo: - return "1.2.840.10008.1.2.4.105"; - - case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2: - return "1.2.840.10008.1.2.4.106"; - - case DicomTransferSyntax_HEVCMainProfileLevel5_1: - return "1.2.840.10008.1.2.4.107"; - - case DicomTransferSyntax_HEVCMain10ProfileLevel5_1: - return "1.2.840.10008.1.2.4.108"; - - case DicomTransferSyntax_RLELossless: - return "1.2.840.10008.1.2.5"; - - case DicomTransferSyntax_RFC2557MimeEncapsulation: - return "1.2.840.10008.1.2.6.1"; - - case DicomTransferSyntax_XML: - return "1.2.840.10008.1.2.6.2"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - bool IsRetiredTransferSyntax(DicomTransferSyntax syntax) - { - switch (syntax) - { - case DicomTransferSyntax_LittleEndianImplicit: - return false; - - case DicomTransferSyntax_LittleEndianExplicit: - return false; - - case DicomTransferSyntax_DeflatedLittleEndianExplicit: - return false; - - case DicomTransferSyntax_BigEndianExplicit: - return false; - - case DicomTransferSyntax_JPEGProcess1: - return false; - - case DicomTransferSyntax_JPEGProcess2_4: - return false; - - case DicomTransferSyntax_JPEGProcess3_5: - return true; - - case DicomTransferSyntax_JPEGProcess6_8: - return true; - - case DicomTransferSyntax_JPEGProcess7_9: - return true; - - case DicomTransferSyntax_JPEGProcess10_12: - return true; - - case DicomTransferSyntax_JPEGProcess11_13: - return true; - - case DicomTransferSyntax_JPEGProcess14: - return false; - - case DicomTransferSyntax_JPEGProcess15: - return true; - - case DicomTransferSyntax_JPEGProcess16_18: - return true; - - case DicomTransferSyntax_JPEGProcess17_19: - return true; - - case DicomTransferSyntax_JPEGProcess20_22: - return true; - - case DicomTransferSyntax_JPEGProcess21_23: - return true; - - case DicomTransferSyntax_JPEGProcess24_26: - return true; - - case DicomTransferSyntax_JPEGProcess25_27: - return true; - - case DicomTransferSyntax_JPEGProcess28: - return true; - - case DicomTransferSyntax_JPEGProcess29: - return true; - - case DicomTransferSyntax_JPEGProcess14SV1: - return false; - - case DicomTransferSyntax_JPEGLSLossless: - return false; - - case DicomTransferSyntax_JPEGLSLossy: - return false; - - case DicomTransferSyntax_JPEG2000LosslessOnly: - return false; - - case DicomTransferSyntax_JPEG2000: - return false; - - case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly: - return false; - - case DicomTransferSyntax_JPEG2000Multicomponent: - return false; - - case DicomTransferSyntax_JPIPReferenced: - return false; - - case DicomTransferSyntax_JPIPReferencedDeflate: - return false; - - case DicomTransferSyntax_MPEG2MainProfileAtMainLevel: - return false; - - case DicomTransferSyntax_MPEG2MainProfileAtHighLevel: - return false; - - case DicomTransferSyntax_MPEG4HighProfileLevel4_1: - return false; - - case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1: - return false; - - case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo: - return false; - - case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo: - return false; - - case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2: - return false; - - case DicomTransferSyntax_HEVCMainProfileLevel5_1: - return false; - - case DicomTransferSyntax_HEVCMain10ProfileLevel5_1: - return false; - - case DicomTransferSyntax_RLELossless: - return false; - - case DicomTransferSyntax_RFC2557MimeEncapsulation: - return true; - - case DicomTransferSyntax_XML: - return true; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - bool LookupTransferSyntax(DicomTransferSyntax& target, - const std::string& uid) - { - if (uid == "1.2.840.10008.1.2") - { - target = DicomTransferSyntax_LittleEndianImplicit; - return true; - } - - if (uid == "1.2.840.10008.1.2.1") - { - target = DicomTransferSyntax_LittleEndianExplicit; - return true; - } - - if (uid == "1.2.840.10008.1.2.1.99") - { - target = DicomTransferSyntax_DeflatedLittleEndianExplicit; - return true; - } - - if (uid == "1.2.840.10008.1.2.2") - { - target = DicomTransferSyntax_BigEndianExplicit; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.50") - { - target = DicomTransferSyntax_JPEGProcess1; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.51") - { - target = DicomTransferSyntax_JPEGProcess2_4; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.52") - { - target = DicomTransferSyntax_JPEGProcess3_5; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.53") - { - target = DicomTransferSyntax_JPEGProcess6_8; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.54") - { - target = DicomTransferSyntax_JPEGProcess7_9; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.55") - { - target = DicomTransferSyntax_JPEGProcess10_12; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.56") - { - target = DicomTransferSyntax_JPEGProcess11_13; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.57") - { - target = DicomTransferSyntax_JPEGProcess14; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.58") - { - target = DicomTransferSyntax_JPEGProcess15; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.59") - { - target = DicomTransferSyntax_JPEGProcess16_18; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.60") - { - target = DicomTransferSyntax_JPEGProcess17_19; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.61") - { - target = DicomTransferSyntax_JPEGProcess20_22; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.62") - { - target = DicomTransferSyntax_JPEGProcess21_23; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.63") - { - target = DicomTransferSyntax_JPEGProcess24_26; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.64") - { - target = DicomTransferSyntax_JPEGProcess25_27; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.65") - { - target = DicomTransferSyntax_JPEGProcess28; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.66") - { - target = DicomTransferSyntax_JPEGProcess29; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.70") - { - target = DicomTransferSyntax_JPEGProcess14SV1; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.80") - { - target = DicomTransferSyntax_JPEGLSLossless; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.81") - { - target = DicomTransferSyntax_JPEGLSLossy; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.90") - { - target = DicomTransferSyntax_JPEG2000LosslessOnly; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.91") - { - target = DicomTransferSyntax_JPEG2000; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.92") - { - target = DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.93") - { - target = DicomTransferSyntax_JPEG2000Multicomponent; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.94") - { - target = DicomTransferSyntax_JPIPReferenced; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.95") - { - target = DicomTransferSyntax_JPIPReferencedDeflate; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.100") - { - target = DicomTransferSyntax_MPEG2MainProfileAtMainLevel; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.101") - { - target = DicomTransferSyntax_MPEG2MainProfileAtHighLevel; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.102") - { - target = DicomTransferSyntax_MPEG4HighProfileLevel4_1; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.103") - { - target = DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.104") - { - target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.105") - { - target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.106") - { - target = DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.107") - { - target = DicomTransferSyntax_HEVCMainProfileLevel5_1; - return true; - } - - if (uid == "1.2.840.10008.1.2.4.108") - { - target = DicomTransferSyntax_HEVCMain10ProfileLevel5_1; - return true; - } - - if (uid == "1.2.840.10008.1.2.5") - { - target = DicomTransferSyntax_RLELossless; - return true; - } - - if (uid == "1.2.840.10008.1.2.6.1") - { - target = DicomTransferSyntax_RFC2557MimeEncapsulation; - return true; - } - - if (uid == "1.2.840.10008.1.2.6.2") - { - target = DicomTransferSyntax_XML; - return true; - } - - return false; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileBuffer.cpp --- a/Core/FileBuffer.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,122 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "FileBuffer.h" - -#include "TemporaryFile.h" -#include "OrthancException.h" - -#include - - -namespace Orthanc -{ - class FileBuffer::PImpl - { - private: - TemporaryFile file_; - boost::filesystem::ofstream stream_; - bool isWriting_; - - public: - PImpl() : - isWriting_(true) - { - stream_.open(file_.GetPath(), std::ofstream::out | std::ofstream::binary); - if (!stream_.good()) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - } - - ~PImpl() - { - if (isWriting_) - { - stream_.close(); - } - } - - void Append(const char* buffer, - size_t size) - { - if (!isWriting_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - if (size > 0) - { - stream_.write(buffer, size); - if (!stream_.good()) - { - stream_.close(); - throw OrthancException(ErrorCode_FileStorageCannotWrite); - } - } - } - - void Read(std::string& target) - { - if (isWriting_) - { - stream_.close(); - isWriting_ = false; - } - - file_.Read(target); - } - }; - - - FileBuffer::FileBuffer() : - pimpl_(new PImpl) - { - } - - - void FileBuffer::Append(const char* buffer, - size_t size) - { - assert(pimpl_.get() != NULL); - pimpl_->Append(buffer, size); - } - - - void FileBuffer::Read(std::string& target) - { - assert(pimpl_.get() != NULL); - pimpl_->Read(target); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileBuffer.h --- a/Core/FileBuffer.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "OrthancFramework.h" - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The class FileBuffer cannot be used in sandboxed environments -#endif - -#include -#include - - -namespace Orthanc -{ - class ORTHANC_PUBLIC FileBuffer : public boost::noncopyable - { - private: - class PImpl; - boost::shared_ptr pimpl_; - - public: - FileBuffer(); - - void Append(const char* buffer, - size_t size); - - void Read(std::string& target); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileStorage/FileInfo.h --- a/Core/FileStorage/FileInfo.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,132 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include -#include -#include "../Enumerations.h" - -namespace Orthanc -{ - struct FileInfo - { - private: - std::string uuid_; - FileContentType contentType_; - - uint64_t uncompressedSize_; - std::string uncompressedMD5_; - - CompressionType compressionType_; - uint64_t compressedSize_; - std::string compressedMD5_; - - public: - FileInfo() - { - } - - /** - * Constructor for an uncompressed attachment. - **/ - FileInfo(const std::string& uuid, - FileContentType contentType, - uint64_t size, - const std::string& md5) : - uuid_(uuid), - contentType_(contentType), - uncompressedSize_(size), - uncompressedMD5_(md5), - compressionType_(CompressionType_None), - compressedSize_(size), - compressedMD5_(md5) - { - } - - /** - * Constructor for a compressed attachment. - **/ - FileInfo(const std::string& uuid, - FileContentType contentType, - uint64_t uncompressedSize, - const std::string& uncompressedMD5, - CompressionType compressionType, - uint64_t compressedSize, - const std::string& compressedMD5) : - uuid_(uuid), - contentType_(contentType), - uncompressedSize_(uncompressedSize), - uncompressedMD5_(uncompressedMD5), - compressionType_(compressionType), - compressedSize_(compressedSize), - compressedMD5_(compressedMD5) - { - } - - const std::string& GetUuid() const - { - return uuid_; - } - - FileContentType GetContentType() const - { - return contentType_; - } - - uint64_t GetUncompressedSize() const - { - return uncompressedSize_; - } - - CompressionType GetCompressionType() const - { - return compressionType_; - } - - uint64_t GetCompressedSize() const - { - return compressedSize_; - } - - const std::string& GetCompressedMD5() const - { - return compressedMD5_; - } - - const std::string& GetUncompressedMD5() const - { - return uncompressedMD5_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileStorage/FilesystemStorage.cpp --- a/Core/FileStorage/FilesystemStorage.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,274 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "FilesystemStorage.h" - -// http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system -// http://stackoverflow.com/questions/446358/storing-a-large-number-of-images - -#include "../Logging.h" -#include "../OrthancException.h" -#include "../Toolbox.h" -#include "../SystemToolbox.h" - -#include - - -static std::string ToString(const boost::filesystem::path& p) -{ -#if BOOST_HAS_FILESYSTEM_V3 == 1 - return p.filename().string(); -#else - return p.filename(); -#endif -} - - -namespace Orthanc -{ - boost::filesystem::path FilesystemStorage::GetPath(const std::string& uuid) const - { - namespace fs = boost::filesystem; - - if (!Toolbox::IsUuid(uuid)) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - fs::path path = root_; - - path /= std::string(&uuid[0], &uuid[2]); - path /= std::string(&uuid[2], &uuid[4]); - path /= uuid; - -#if BOOST_HAS_FILESYSTEM_V3 == 1 - path.make_preferred(); -#endif - - return path; - } - - FilesystemStorage::FilesystemStorage(std::string root) - { - //root_ = boost::filesystem::absolute(root).string(); - root_ = root; - - SystemToolbox::MakeDirectory(root); - } - - - - static const char* GetDescriptionInternal(FileContentType content) - { - // This function is for logging only (internal use), a more - // fully-featured version is available in ServerEnumerations.cpp - switch (content) - { - case FileContentType_Unknown: - return "Unknown"; - - case FileContentType_Dicom: - return "DICOM"; - - case FileContentType_DicomAsJson: - return "JSON summary of DICOM"; - - default: - return "User-defined"; - } - } - - - void FilesystemStorage::Create(const std::string& uuid, - const void* content, - size_t size, - FileContentType type) - { - LOG(INFO) << "Creating attachment \"" << uuid << "\" of \"" << GetDescriptionInternal(type) - << "\" type (size: " << (size / (1024 * 1024) + 1) << "MB)"; - - boost::filesystem::path path; - - path = GetPath(uuid); - - if (boost::filesystem::exists(path)) - { - // Extremely unlikely case: This Uuid has already been created - // in the past. - throw OrthancException(ErrorCode_InternalError); - } - - if (boost::filesystem::exists(path.parent_path())) - { - if (!boost::filesystem::is_directory(path.parent_path())) - { - throw OrthancException(ErrorCode_DirectoryOverFile); - } - } - else - { - if (!boost::filesystem::create_directories(path.parent_path())) - { - throw OrthancException(ErrorCode_FileStorageCannotWrite); - } - } - - SystemToolbox::WriteFile(content, size, path.string()); - } - - - void FilesystemStorage::Read(std::string& content, - const std::string& uuid, - FileContentType type) - { - LOG(INFO) << "Reading attachment \"" << uuid << "\" of \"" << GetDescriptionInternal(type) - << "\" content type"; - - content.clear(); - SystemToolbox::ReadFile(content, GetPath(uuid).string()); - } - - - uintmax_t FilesystemStorage::GetSize(const std::string& uuid) const - { - boost::filesystem::path path = GetPath(uuid); - return boost::filesystem::file_size(path); - } - - - - void FilesystemStorage::ListAllFiles(std::set& result) const - { - namespace fs = boost::filesystem; - - result.clear(); - - if (fs::exists(root_) && fs::is_directory(root_)) - { - for (fs::recursive_directory_iterator current(root_), end; current != end ; ++current) - { - if (SystemToolbox::IsRegularFile(current->path().string())) - { - try - { - fs::path d = current->path(); - std::string uuid = ToString(d); - if (Toolbox::IsUuid(uuid)) - { - fs::path p0 = d.parent_path().parent_path().parent_path(); - std::string p1 = ToString(d.parent_path().parent_path()); - std::string p2 = ToString(d.parent_path()); - if (p1.length() == 2 && - p2.length() == 2 && - p1 == uuid.substr(0, 2) && - p2 == uuid.substr(2, 2) && - p0 == root_) - { - result.insert(uuid); - } - } - } - catch (fs::filesystem_error&) - { - } - } - } - } - } - - - void FilesystemStorage::Clear() - { - namespace fs = boost::filesystem; - typedef std::set List; - - List result; - ListAllFiles(result); - - for (List::const_iterator it = result.begin(); it != result.end(); ++it) - { - Remove(*it, FileContentType_Unknown /*ignored in this class*/); - } - } - - - void FilesystemStorage::Remove(const std::string& uuid, - FileContentType type) - { - LOG(INFO) << "Deleting attachment \"" << uuid << "\" of type " << static_cast(type); - - namespace fs = boost::filesystem; - - fs::path p = GetPath(uuid); - - try - { - fs::remove(p); - } - catch (...) - { - // Ignore the error - } - - // Remove the two parent directories, ignoring the error code if - // these directories are not empty - - try - { -#if BOOST_HAS_FILESYSTEM_V3 == 1 - boost::system::error_code err; - fs::remove(p.parent_path(), err); - fs::remove(p.parent_path().parent_path(), err); -#else - fs::remove(p.parent_path()); - fs::remove(p.parent_path().parent_path()); -#endif - } - catch (...) - { - // Ignore the error - } - } - - - uintmax_t FilesystemStorage::GetCapacity() const - { - return boost::filesystem::space(root_).capacity; - } - - uintmax_t FilesystemStorage::GetAvailableSpace() const - { - return boost::filesystem::space(root_).available; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileStorage/FilesystemStorage.h --- a/Core/FileStorage/FilesystemStorage.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,90 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../OrthancFramework.h" - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The class FilesystemStorage cannot be used in sandboxed environments -#endif - -#include "IStorageArea.h" - -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC FilesystemStorage : public IStorageArea - { - // TODO REMOVE THIS - friend class FilesystemHttpSender; - friend class FileStorageAccessor; - - private: - boost::filesystem::path root_; - - boost::filesystem::path GetPath(const std::string& uuid) const; - - public: - explicit FilesystemStorage(std::string root); - - virtual void Create(const std::string& uuid, - const void* content, - size_t size, - FileContentType type); - - virtual void Read(std::string& content, - const std::string& uuid, - FileContentType type); - - virtual void Remove(const std::string& uuid, - FileContentType type); - - void ListAllFiles(std::set& result) const; - - uintmax_t GetSize(const std::string& uuid) const; - - void Clear(); - - uintmax_t GetCapacity() const; - - uintmax_t GetAvailableSpace() const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileStorage/IStorageArea.h --- a/Core/FileStorage/IStorageArea.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include -#include - -namespace Orthanc -{ - class IStorageArea : public boost::noncopyable - { - public: - virtual ~IStorageArea() - { - } - - virtual void Create(const std::string& uuid, - const void* content, - size_t size, - FileContentType type) = 0; - - virtual void Read(std::string& content, - const std::string& uuid, - FileContentType type) = 0; - - virtual void Remove(const std::string& uuid, - FileContentType type) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileStorage/MemoryStorageArea.cpp --- a/Core/FileStorage/MemoryStorageArea.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,128 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "MemoryStorageArea.h" - -#include "../OrthancException.h" -#include "../Logging.h" - -namespace Orthanc -{ - MemoryStorageArea::~MemoryStorageArea() - { - for (Content::iterator it = content_.begin(); it != content_.end(); ++it) - { - if (it->second != NULL) - { - delete it->second; - } - } - } - - void MemoryStorageArea::Create(const std::string& uuid, - const void* content, - size_t size, - FileContentType type) - { - LOG(INFO) << "Creating attachment \"" << uuid << "\" of \"" << static_cast(type) - << "\" type (size: " << (size / (1024 * 1024) + 1) << "MB)"; - - boost::mutex::scoped_lock lock(mutex_); - - if (size != 0 && - content == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - else if (content_.find(uuid) != content_.end()) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - content_[uuid] = new std::string(reinterpret_cast(content), size); - } - } - - - void MemoryStorageArea::Read(std::string& content, - const std::string& uuid, - FileContentType type) - { - LOG(INFO) << "Reading attachment \"" << uuid << "\" of \"" - << static_cast(type) << "\" content type"; - - boost::mutex::scoped_lock lock(mutex_); - - Content::const_iterator found = content_.find(uuid); - - if (found == content_.end()) - { - throw OrthancException(ErrorCode_InexistentFile); - } - else if (found->second == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - content.assign(*found->second); - } - } - - - void MemoryStorageArea::Remove(const std::string& uuid, - FileContentType type) - { - LOG(INFO) << "Deleting attachment \"" << uuid << "\" of type " << static_cast(type); - - boost::mutex::scoped_lock lock(mutex_); - - Content::iterator found = content_.find(uuid); - - if (found == content_.end()) - { - // Ignore second removal - } - else if (found->second == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - else - { - delete found->second; - content_.erase(found); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileStorage/MemoryStorageArea.h --- a/Core/FileStorage/MemoryStorageArea.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IStorageArea.h" - -#include -#include - -namespace Orthanc -{ - class MemoryStorageArea : public IStorageArea - { - private: - typedef std::map Content; - - boost::mutex mutex_; - Content content_; - - public: - virtual ~MemoryStorageArea(); - - virtual void Create(const std::string& uuid, - const void* content, - size_t size, - FileContentType type); - - virtual void Read(std::string& content, - const std::string& uuid, - FileContentType type); - - virtual void Remove(const std::string& uuid, - FileContentType type); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileStorage/StorageAccessor.cpp --- a/Core/FileStorage/StorageAccessor.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,246 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "StorageAccessor.h" - -#include "../Compatibility.h" -#include "../Compression/ZlibCompressor.h" -#include "../MetricsRegistry.h" -#include "../OrthancException.h" -#include "../Toolbox.h" - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 -# include "../HttpServer/HttpStreamTranscoder.h" -#endif - - -static const std::string METRICS_CREATE = "orthanc_storage_create_duration_ms"; -static const std::string METRICS_READ = "orthanc_storage_read_duration_ms"; -static const std::string METRICS_REMOVE = "orthanc_storage_remove_duration_ms"; - - -namespace Orthanc -{ - class StorageAccessor::MetricsTimer : public boost::noncopyable - { - private: - std::unique_ptr timer_; - - public: - MetricsTimer(StorageAccessor& that, - const std::string& name) - { - if (that.metrics_ != NULL) - { - timer_.reset(new MetricsRegistry::Timer(*that.metrics_, name)); - } - } - }; - - - FileInfo StorageAccessor::Write(const void* data, - size_t size, - FileContentType type, - CompressionType compression, - bool storeMd5) - { - std::string uuid = Toolbox::GenerateUuid(); - - std::string md5; - - if (storeMd5) - { - Toolbox::ComputeMD5(md5, data, size); - } - - switch (compression) - { - case CompressionType_None: - { - MetricsTimer timer(*this, METRICS_CREATE); - - area_.Create(uuid, data, size, type); - return FileInfo(uuid, type, size, md5); - } - - case CompressionType_ZlibWithSize: - { - ZlibCompressor zlib; - - std::string compressed; - zlib.Compress(compressed, data, size); - - std::string compressedMD5; - - if (storeMd5) - { - Toolbox::ComputeMD5(compressedMD5, compressed); - } - - { - MetricsTimer timer(*this, METRICS_CREATE); - - if (compressed.size() > 0) - { - area_.Create(uuid, &compressed[0], compressed.size(), type); - } - else - { - area_.Create(uuid, NULL, 0, type); - } - } - - return FileInfo(uuid, type, size, md5, - CompressionType_ZlibWithSize, compressed.size(), compressedMD5); - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void StorageAccessor::Read(std::string& content, - const FileInfo& info) - { - switch (info.GetCompressionType()) - { - case CompressionType_None: - { - MetricsTimer timer(*this, METRICS_READ); - area_.Read(content, info.GetUuid(), info.GetContentType()); - break; - } - - case CompressionType_ZlibWithSize: - { - ZlibCompressor zlib; - - std::string compressed; - - { - MetricsTimer timer(*this, METRICS_READ); - area_.Read(compressed, info.GetUuid(), info.GetContentType()); - } - - IBufferCompressor::Uncompress(content, zlib, compressed); - break; - } - - default: - { - throw OrthancException(ErrorCode_NotImplemented); - } - } - - // TODO Check the validity of the uncompressed MD5? - } - - - void StorageAccessor::ReadRaw(std::string& content, - const FileInfo& info) - { - MetricsTimer timer(*this, METRICS_READ); - area_.Read(content, info.GetUuid(), info.GetContentType()); - } - - - void StorageAccessor::Remove(const std::string& fileUuid, - FileContentType type) - { - MetricsTimer timer(*this, METRICS_REMOVE); - area_.Remove(fileUuid, type); - } - - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 - void StorageAccessor::SetupSender(BufferHttpSender& sender, - const FileInfo& info, - const std::string& mime) - { - { - MetricsTimer timer(*this, METRICS_READ); - area_.Read(sender.GetBuffer(), info.GetUuid(), info.GetContentType()); - } - - sender.SetContentType(mime); - - const char* extension; - switch (info.GetContentType()) - { - case FileContentType_Dicom: - extension = ".dcm"; - break; - - case FileContentType_DicomAsJson: - extension = ".json"; - break; - - default: - // Non-standard content type - extension = ""; - } - - sender.SetContentFilename(info.GetUuid() + std::string(extension)); - } -#endif - - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 - void StorageAccessor::AnswerFile(HttpOutput& output, - const FileInfo& info, - const std::string& mime) - { - BufferHttpSender sender; - SetupSender(sender, info, mime); - - HttpStreamTranscoder transcoder(sender, info.GetCompressionType()); - output.Answer(transcoder); - } -#endif - - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 - void StorageAccessor::AnswerFile(RestApiOutput& output, - const FileInfo& info, - const std::string& mime) - { - BufferHttpSender sender; - SetupSender(sender, info, mime); - - HttpStreamTranscoder transcoder(sender, info.GetCompressionType()); - output.AnswerStream(transcoder); - } -#endif -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/FileStorage/StorageAccessor.h --- a/Core/FileStorage/StorageAccessor.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The class StorageAccessor cannot be used in sandboxed environments -#endif - -#if !defined(ORTHANC_ENABLE_CIVETWEB) -# error Macro ORTHANC_ENABLE_CIVETWEB must be defined to use this file -#endif - -#if !defined(ORTHANC_ENABLE_MONGOOSE) -# error Macro ORTHANC_ENABLE_MONGOOSE must be defined to use this file -#endif - -#include "IStorageArea.h" -#include "FileInfo.h" - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 -# include "../HttpServer/BufferHttpSender.h" -# include "../RestApi/RestApiOutput.h" -#endif - -#include -#include -#include -#include - -namespace Orthanc -{ - class MetricsRegistry; - - /** - * This class handles the compression/decompression of the raw files - * contained in the storage area, and monitors timing metrics (if - * enabled). - **/ - class StorageAccessor : boost::noncopyable - { - private: - class MetricsTimer; - - IStorageArea& area_; - MetricsRegistry* metrics_; - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 - void SetupSender(BufferHttpSender& sender, - const FileInfo& info, - const std::string& mime); -#endif - - public: - StorageAccessor(IStorageArea& area) : - area_(area), - metrics_(NULL) - { - } - - StorageAccessor(IStorageArea& area, - MetricsRegistry& metrics) : - area_(area), - metrics_(&metrics) - { - } - - FileInfo Write(const void* data, - size_t size, - FileContentType type, - CompressionType compression, - bool storeMd5); - - FileInfo Write(const std::string& data, - FileContentType type, - CompressionType compression, - bool storeMd5) - { - return Write((data.size() == 0 ? NULL : data.c_str()), - data.size(), type, compression, storeMd5); - } - - void Read(std::string& content, - const FileInfo& info); - - void ReadRaw(std::string& content, - const FileInfo& info); - - void Remove(const std::string& fileUuid, - FileContentType type); - - void Remove(const FileInfo& info) - { - Remove(info.GetUuid(), info.GetContentType()); - } - -#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1 - void AnswerFile(HttpOutput& output, - const FileInfo& info, - MimeType mime) - { - AnswerFile(output, info, EnumerationToString(mime)); - } - - void AnswerFile(HttpOutput& output, - const FileInfo& info, - const std::string& mime); - - void AnswerFile(RestApiOutput& output, - const FileInfo& info, - MimeType mime) - { - AnswerFile(output, info, EnumerationToString(mime)); - } - - void AnswerFile(RestApiOutput& output, - const FileInfo& info, - const std::string& mime); -#endif - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpClient.cpp --- a/Core/HttpClient.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1214 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "HttpClient.h" - -#include "Toolbox.h" -#include "OrthancException.h" -#include "Logging.h" -#include "ChunkedBuffer.h" -#include "SystemToolbox.h" - -#include -#include -#include -#include - -// Default timeout = 60 seconds (in Orthanc <= 1.5.6, it was 10 seconds) -static const unsigned int DEFAULT_HTTP_TIMEOUT = 60; - - -#if ORTHANC_ENABLE_PKCS11 == 1 -# include "Pkcs11.h" -#endif - - -extern "C" -{ - static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status) - { - if (code == CURLE_OK) - { - code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status); - return code; - } - else - { - LOG(ERROR) << "Error code " << static_cast(code) - << " in libcurl: " << curl_easy_strerror(code); - *status = 0; - return code; - } - } -} - -// This is a dummy wrapper function to suppress any OpenSSL-related -// problem in valgrind. Inlining is prevented. -#if defined(__GNUC__) || defined(__clang__) - __attribute__((noinline)) -#endif -static CURLcode OrthancHttpClientPerformSSL(CURL* curl, long* status) -{ -#if ORTHANC_ENABLE_SSL == 1 - return GetHttpStatus(curl_easy_perform(curl), curl, status); -#else - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "Orthanc was compiled without SSL support, " - "cannot make HTTPS request"); -#endif -} - - - -namespace Orthanc -{ - static CURLcode CheckCode(CURLcode code) - { - if (code == CURLE_NOT_BUILT_IN) - { - throw OrthancException(ErrorCode_InternalError, - "Your libcurl does not contain a required feature, " - "please recompile Orthanc with -DUSE_SYSTEM_CURL=OFF"); - } - - if (code != CURLE_OK) - { - throw OrthancException(ErrorCode_NetworkProtocol, - "libCURL error: " + std::string(curl_easy_strerror(code))); - } - - return code; - } - - - // RAII pattern around a "curl_slist" - class HttpClient::CurlHeaders : public boost::noncopyable - { - private: - struct curl_slist *content_; - bool isChunkedTransfer_; - bool hasExpect_; - - public: - CurlHeaders() : - content_(NULL), - isChunkedTransfer_(false), - hasExpect_(false) - { - } - - CurlHeaders(const HttpClient::HttpHeaders& headers) - { - for (HttpClient::HttpHeaders::const_iterator - it = headers.begin(); it != headers.end(); ++it) - { - AddHeader(it->first, it->second); - } - } - - ~CurlHeaders() - { - Clear(); - } - - bool IsEmpty() const - { - return content_ == NULL; - } - - void Clear() - { - if (content_ != NULL) - { - curl_slist_free_all(content_); - content_ = NULL; - } - - isChunkedTransfer_ = false; - hasExpect_ = false; - } - - void AddHeader(const std::string& key, - const std::string& value) - { - if (boost::iequals(key, "Expect")) - { - hasExpect_ = true; - } - - if (boost::iequals(key, "Transfer-Encoding") && - value == "chunked") - { - isChunkedTransfer_ = true; - } - - std::string item = key + ": " + value; - - struct curl_slist *tmp = curl_slist_append(content_, item.c_str()); - - if (tmp == NULL) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - else - { - content_ = tmp; - } - } - - void Assign(CURL* curl) const - { - CheckCode(curl_easy_setopt(curl, CURLOPT_HTTPHEADER, content_)); - } - - bool HasExpect() const - { - return hasExpect_; - } - - bool IsChunkedTransfer() const - { - return isChunkedTransfer_; - } - }; - - - class HttpClient::CurlRequestBody : public boost::noncopyable - { - private: - HttpClient::IRequestBody* body_; - std::string sourceBuffer_; - size_t sourceBufferTransmittedSize_; - - size_t CallbackInternal(char* curlBuffer, - size_t curlBufferSize) - { - if (body_ == NULL) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - if (curlBufferSize == 0) - { - throw OrthancException(ErrorCode_InternalError); - } - - // Read chunks from the body stream so as to fill the target buffer - size_t curlBufferFilledSize = 0; - size_t sourceRemainingSize = sourceBuffer_.size() - sourceBufferTransmittedSize_; - bool hasMore = true; - - while (sourceRemainingSize < curlBufferSize && hasMore) - { - if (sourceRemainingSize > 0) - { - // transmit the end of current source buffer - memcpy(curlBuffer + curlBufferFilledSize, - sourceBuffer_.data() + sourceBufferTransmittedSize_, sourceRemainingSize); - - curlBufferFilledSize += sourceRemainingSize; - } - - // start filling a new source buffer - sourceBufferTransmittedSize_ = 0; - sourceBuffer_.clear(); - - hasMore = body_->ReadNextChunk(sourceBuffer_); - - sourceRemainingSize = sourceBuffer_.size(); - } - - if (sourceRemainingSize > 0 && - curlBufferSize > curlBufferFilledSize) - { - size_t s = std::min(sourceRemainingSize, curlBufferSize - curlBufferFilledSize); - - memcpy(curlBuffer + curlBufferFilledSize, - sourceBuffer_.data() + sourceBufferTransmittedSize_, s); - - sourceBufferTransmittedSize_ += s; - curlBufferFilledSize += s; - } - - return curlBufferFilledSize; - } - - public: - CurlRequestBody() : - body_(NULL), - sourceBufferTransmittedSize_(0) - { - } - - void SetBody(HttpClient::IRequestBody& body) - { - body_ = &body; - sourceBufferTransmittedSize_ = 0; - sourceBuffer_.clear(); - } - - void Clear() - { - body_ = NULL; - sourceBufferTransmittedSize_ = 0; - sourceBuffer_.clear(); - } - - bool IsValid() const - { - return body_ != NULL; - } - - static size_t Callback(char *buffer, size_t size, size_t nitems, void *userdata) - { - try - { - assert(userdata != NULL); - return reinterpret_cast(userdata)-> - CallbackInternal(buffer, size * nitems); - } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception while streaming HTTP body: " << e.What(); - return CURL_READFUNC_ABORT; - } - catch (...) - { - LOG(ERROR) << "Native exception while streaming HTTP body"; - return CURL_READFUNC_ABORT; - } - } - }; - - - class HttpClient::CurlAnswer : public boost::noncopyable - { - private: - HttpClient::IAnswer& answer_; - bool headersLowerCase_; - - public: - CurlAnswer(HttpClient::IAnswer& answer, - bool headersLowerCase) : - answer_(answer), - headersLowerCase_(headersLowerCase) - { - } - - static size_t HeaderCallback(void *buffer, size_t size, size_t nmemb, void *userdata) - { - try - { - assert(userdata != NULL); - CurlAnswer& that = *(static_cast(userdata)); - - size_t length = size * nmemb; - if (length == 0) - { - return 0; - } - else - { - std::string s(reinterpret_cast(buffer), length); - std::size_t colon = s.find(':'); - std::size_t eol = s.find("\r\n"); - if (colon != std::string::npos && - eol != std::string::npos) - { - std::string tmp(s.substr(0, colon)); - - if (that.headersLowerCase_) - { - Toolbox::ToLowerCase(tmp); - } - - std::string key = Toolbox::StripSpaces(tmp); - - if (!key.empty()) - { - std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol)); - that.answer_.AddHeader(key, value); - } - } - - return length; - } - } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception while streaming HTTP body: " << e.What(); - return CURL_READFUNC_ABORT; - } - catch (...) - { - LOG(ERROR) << "Native exception while streaming HTTP body"; - return CURL_READFUNC_ABORT; - } - } - - static size_t BodyCallback(void *buffer, size_t size, size_t nmemb, void *userdata) - { - try - { - assert(userdata != NULL); - CurlAnswer& that = *(static_cast(userdata)); - - size_t length = size * nmemb; - if (length == 0) - { - return 0; - } - else - { - that.answer_.AddChunk(buffer, length); - return length; - } - } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception while streaming HTTP body: " << e.What(); - return CURL_READFUNC_ABORT; - } - catch (...) - { - LOG(ERROR) << "Native exception while streaming HTTP body"; - return CURL_READFUNC_ABORT; - } - } - }; - - - class HttpClient::DefaultAnswer : public HttpClient::IAnswer - { - private: - ChunkedBuffer answer_; - HttpHeaders* headers_; - - public: - DefaultAnswer() : headers_(NULL) - { - } - - void SetHeaders(HttpHeaders& headers) - { - headers_ = &headers; - headers_->clear(); - } - - void FlattenBody(std::string& target) - { - answer_.Flatten(target); - } - - virtual void AddHeader(const std::string& key, - const std::string& value) - { - if (headers_ != NULL) - { - (*headers_) [key] = value; - } - } - - virtual void AddChunk(const void* data, - size_t size) - { - answer_.AddChunk(data, size); - } - }; - - - class HttpClient::GlobalParameters - { - private: - boost::mutex mutex_; - bool httpsVerifyPeers_; - std::string httpsCACertificates_; - std::string proxy_; - long timeout_; - bool verbose_; - - GlobalParameters() : - httpsVerifyPeers_(true), - timeout_(0), - verbose_(false) - { - } - - public: - // Singleton pattern - static GlobalParameters& GetInstance() - { - static GlobalParameters parameters; - return parameters; - } - - void ConfigureSsl(bool httpsVerifyPeers, - const std::string& httpsCACertificates) - { - boost::mutex::scoped_lock lock(mutex_); - httpsVerifyPeers_ = httpsVerifyPeers; - httpsCACertificates_ = httpsCACertificates; - } - - void GetSslConfiguration(bool& httpsVerifyPeers, - std::string& httpsCACertificates) - { - boost::mutex::scoped_lock lock(mutex_); - httpsVerifyPeers = httpsVerifyPeers_; - httpsCACertificates = httpsCACertificates_; - } - - void SetDefaultProxy(const std::string& proxy) - { - LOG(INFO) << "Setting the default proxy for HTTP client connections: " << proxy; - - { - boost::mutex::scoped_lock lock(mutex_); - proxy_ = proxy; - } - } - - void GetDefaultProxy(std::string& target) - { - boost::mutex::scoped_lock lock(mutex_); - target = proxy_; - } - - void SetDefaultTimeout(long seconds) - { - LOG(INFO) << "Setting the default timeout for HTTP client connections: " << seconds << " seconds"; - - { - boost::mutex::scoped_lock lock(mutex_); - timeout_ = seconds; - } - } - - long GetDefaultTimeout() - { - boost::mutex::scoped_lock lock(mutex_); - return timeout_; - } - -#if ORTHANC_ENABLE_PKCS11 == 1 - bool IsPkcs11Initialized() - { - boost::mutex::scoped_lock lock(mutex_); - return Pkcs11::IsInitialized(); - } - - void InitializePkcs11(const std::string& module, - const std::string& pin, - bool verbose) - { - boost::mutex::scoped_lock lock(mutex_); - Pkcs11::Initialize(module, pin, verbose); - } -#endif - - bool IsDefaultVerbose() const - { - return verbose_; - } - - void SetDefaultVerbose(bool verbose) - { - verbose_ = verbose; - } - }; - - - struct HttpClient::PImpl - { - CURL* curl_; - CurlHeaders defaultPostHeaders_; - CurlHeaders defaultChunkedHeaders_; - CurlHeaders userHeaders_; - CurlRequestBody requestBody_; - }; - - - void HttpClient::ThrowException(HttpStatus status) - { - switch (status) - { - case HttpStatus_400_BadRequest: - throw OrthancException(ErrorCode_BadRequest); - - case HttpStatus_401_Unauthorized: - case HttpStatus_403_Forbidden: - throw OrthancException(ErrorCode_Unauthorized); - - case HttpStatus_404_NotFound: - throw OrthancException(ErrorCode_UnknownResource); - - default: - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - - - /*static int CurlDebugCallback(CURL *handle, - curl_infotype type, - char *data, - size_t size, - void *userptr) - { - switch (type) - { - case CURLINFO_TEXT: - case CURLINFO_HEADER_IN: - case CURLINFO_HEADER_OUT: - case CURLINFO_SSL_DATA_IN: - case CURLINFO_SSL_DATA_OUT: - case CURLINFO_END: - case CURLINFO_DATA_IN: - case CURLINFO_DATA_OUT: - { - std::string s(data, size); - LOG(INFO) << "libcurl: " << s; - break; - } - - default: - break; - } - - return 0; - }*/ - - - void HttpClient::Setup() - { - pimpl_->defaultPostHeaders_.AddHeader("Expect", ""); - pimpl_->defaultChunkedHeaders_.AddHeader("Expect", ""); - pimpl_->defaultChunkedHeaders_.AddHeader("Transfer-Encoding", "chunked"); - - pimpl_->curl_ = curl_easy_init(); - - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlAnswer::HeaderCallback)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlAnswer::BodyCallback)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); - - // This fixes the "longjmp causes uninitialized stack frame" crash - // that happens on modern Linux versions. - // http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOSIGNAL, 1)); - - url_ = ""; - method_ = HttpMethod_Get; - lastStatus_ = HttpStatus_None; - SetVerbose(GlobalParameters::GetInstance().IsDefaultVerbose()); - timeout_ = GlobalParameters::GetInstance().GetDefaultTimeout(); - GlobalParameters::GetInstance().GetDefaultProxy(proxy_); - GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_); - } - - - HttpClient::HttpClient() : - pimpl_(new PImpl), - verifyPeers_(true), - pkcs11Enabled_(false), - headersToLowerCase_(true), - redirectionFollowed_(true) - { - Setup(); - } - - - HttpClient::HttpClient(const WebServiceParameters& service, - const std::string& uri) : - pimpl_(new PImpl), - verifyPeers_(true), - headersToLowerCase_(true), - redirectionFollowed_(true) - { - Setup(); - - if (service.GetUsername().size() != 0 && - service.GetPassword().size() != 0) - { - SetCredentials(service.GetUsername().c_str(), - service.GetPassword().c_str()); - } - - if (!service.GetCertificateFile().empty()) - { - SetClientCertificate(service.GetCertificateFile(), - service.GetCertificateKeyFile(), - service.GetCertificateKeyPassword()); - } - - SetPkcs11Enabled(service.IsPkcs11Enabled()); - - SetUrl(service.GetUrl() + uri); - - for (WebServiceParameters::Dictionary::const_iterator - it = service.GetHttpHeaders().begin(); - it != service.GetHttpHeaders().end(); ++it) - { - AddHeader(it->first, it->second); - } - } - - - HttpClient::~HttpClient() - { - curl_easy_cleanup(pimpl_->curl_); - } - - - void HttpClient::SetBody(const std::string& data) - { - body_ = data; - pimpl_->requestBody_.Clear(); - } - - - void HttpClient::SetBody(IRequestBody& body) - { - body_.clear(); - pimpl_->requestBody_.SetBody(body); - } - - - void HttpClient::ClearBody() - { - body_.clear(); - pimpl_->requestBody_.Clear(); - } - - - void HttpClient::SetVerbose(bool isVerbose) - { - isVerbose_ = isVerbose; - - if (isVerbose_) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1)); - //CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_DEBUGFUNCTION, &CurlDebugCallback)); - } - else - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0)); - } - } - - - void HttpClient::AddHeader(const std::string& key, - const std::string& value) - { - if (key.empty()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - pimpl_->userHeaders_.AddHeader(key, value); - } - } - - - void HttpClient::ClearHeaders() - { - pimpl_->userHeaders_.Clear(); - } - - - bool HttpClient::ApplyInternal(CurlAnswer& answer) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &answer)); - -#if ORTHANC_ENABLE_SSL == 1 - // Setup HTTPS-related options - - if (verifyPeers_) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2)); // libcurl default is strict verifyhost - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); - } - else - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 0)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); - } -#endif - - // Setup the HTTPS client certificate - if (!clientCertificateFile_.empty() && - pkcs11Enabled_) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Cannot enable both client certificates and PKCS#11 authentication"); - } - - if (pkcs11Enabled_) - { -#if ORTHANC_ENABLE_PKCS11 == 1 - if (GlobalParameters::GetInstance().IsPkcs11Initialized()) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, Pkcs11::GetEngineIdentifier())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "ENG")); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "ENG")); - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "Cannot use PKCS#11 for a HTTPS request, " - "because it has not been initialized"); - } -#else - throw OrthancException(ErrorCode_InternalError, - "This version of Orthanc is compiled without support for PKCS#11"); -#endif - } - else if (!clientCertificateFile_.empty()) - { -#if ORTHANC_ENABLE_SSL == 1 - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM")); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str())); - - if (!clientCertificateKeyPassword_.empty()) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str())); - } - - // NB: If no "clientKeyFile_" is provided, the key must be - // prepended to the certificate file - if (!clientCertificateKeyFile_.empty()) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM")); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str())); - } -#else - throw OrthancException(ErrorCode_InternalError, - "This version of Orthanc is compiled without OpenSSL support, " - "cannot use HTTPS client authentication"); -#endif - } - - // Reset the parameters from previous calls to Apply() - pimpl_->userHeaders_.Assign(pimpl_->curl_); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 0L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, NULL)); - - if (redirectionFollowed_) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1L)); - } - else - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 0L)); - } - - // Set timeouts - if (timeout_ <= 0) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, DEFAULT_HTTP_TIMEOUT)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, DEFAULT_HTTP_TIMEOUT)); - } - else - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, timeout_)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, timeout_)); - } - - if (credentials_.size() != 0) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str())); - } - - if (proxy_.size() != 0) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str())); - } - - switch (method_) - { - case HttpMethod_Get: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L)); - break; - - case HttpMethod_Post: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); - - break; - - case HttpMethod_Delete: - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE")); - break; - - case HttpMethod_Put: - // http://stackoverflow.com/a/7570281/881731: Don't use - // CURLOPT_PUT if there is a body - - // CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); - - curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "PUT"); /* !!! */ - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - if (method_ == HttpMethod_Post || - method_ == HttpMethod_Put) - { - if (!pimpl_->userHeaders_.IsEmpty() && - !pimpl_->userHeaders_.HasExpect()) - { - LOG(INFO) << "For performance, the HTTP header \"Expect\" should be set to empty string in POST/PUT requests"; - } - - if (pimpl_->requestBody_.IsValid()) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, CurlRequestBody::Callback)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READDATA, &pimpl_->requestBody_)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, -1L)); - - if (pimpl_->userHeaders_.IsEmpty()) - { - pimpl_->defaultChunkedHeaders_.Assign(pimpl_->curl_); - } - else if (!pimpl_->userHeaders_.IsChunkedTransfer()) - { - LOG(WARNING) << "The HTTP header \"Transfer-Encoding\" must be set to \"chunked\" " - << "if streaming a chunked body in POST/PUT requests"; - } - } - else - { - // Disable possible previous stream transfers - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_READFUNCTION, NULL)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_UPLOAD, 0)); - - if (pimpl_->userHeaders_.IsChunkedTransfer()) - { - LOG(WARNING) << "The HTTP header \"Transfer-Encoding\" must only be set " - << "if streaming a chunked body in POST/PUT requests"; - } - - if (pimpl_->userHeaders_.IsEmpty()) - { - pimpl_->defaultPostHeaders_.Assign(pimpl_->curl_); - } - - if (body_.size() > 0) - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, body_.c_str())); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, body_.size())); - } - else - { - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); - } - } - } - - - // Do the actual request - CURLcode code; - long status = 0; - - CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &answer)); - - const boost::posix_time::ptime start = boost::posix_time::microsec_clock::universal_time(); - - if (boost::starts_with(url_, "https://")) - { - code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status); - } - else - { - code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status); - } - - const boost::posix_time::ptime end = boost::posix_time::microsec_clock::universal_time(); - - LOG(INFO) << "HTTP status code " << status << " in " - << ((end - start).total_milliseconds()) << " ms after " - << EnumerationToString(method_) << " request on: " << url_; - - if (isVerbose_) - { - LOG(INFO) << "cURL status code: " << code; - } - - CheckCode(code); - - if (status == 0) - { - // This corresponds to a call to an inexistent host - lastStatus_ = HttpStatus_500_InternalServerError; - } - else - { - lastStatus_ = static_cast(status); - } - - if (status >= 200 && status < 300) - { - return true; // Success - } - else - { - LOG(ERROR) << "Error in HTTP request, received HTTP status " << status - << " (" << EnumerationToString(lastStatus_) << ")"; - return false; - } - } - - - bool HttpClient::ApplyInternal(std::string& answerBody, - HttpHeaders* answerHeaders) - { - answerBody.clear(); - - DefaultAnswer answer; - - if (answerHeaders != NULL) - { - answer.SetHeaders(*answerHeaders); - } - - CurlAnswer wrapper(answer, headersToLowerCase_); - - if (ApplyInternal(wrapper)) - { - answer.FlattenBody(answerBody); - return true; - } - else - { - return false; - } - } - - - bool HttpClient::ApplyInternal(Json::Value& answerBody, - HttpClient::HttpHeaders* answerHeaders) - { - std::string s; - if (ApplyInternal(s, answerHeaders)) - { - Json::Reader reader; - return reader.parse(s, answerBody); - } - else - { - return false; - } - } - - - void HttpClient::SetCredentials(const char* username, - const char* password) - { - credentials_ = std::string(username) + ":" + std::string(password); - } - - - void HttpClient::ConfigureSsl(bool httpsVerifyPeers, - const std::string& httpsVerifyCertificates) - { -#if ORTHANC_ENABLE_SSL == 1 - if (httpsVerifyPeers) - { - if (httpsVerifyCertificates.empty()) - { - LOG(WARNING) << "No certificates are provided to validate peers, " - << "set \"HttpsCACertificates\" if you need to do HTTPS requests"; - } - else - { - LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << httpsVerifyCertificates; - } - } - else - { - LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled"; - } -#endif - - GlobalParameters::GetInstance().ConfigureSsl(httpsVerifyPeers, httpsVerifyCertificates); - } - - - void HttpClient::GlobalInitialize() - { -#if ORTHANC_ENABLE_SSL == 1 - CheckCode(curl_global_init(CURL_GLOBAL_ALL)); -#else - CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL)); -#endif - } - - - void HttpClient::GlobalFinalize() - { - curl_global_cleanup(); - -#if ORTHANC_ENABLE_PKCS11 == 1 - Pkcs11::Finalize(); -#endif - } - - - void HttpClient::SetDefaultVerbose(bool verbose) - { - GlobalParameters::GetInstance().SetDefaultVerbose(verbose); - } - - - void HttpClient::SetDefaultProxy(const std::string& proxy) - { - GlobalParameters::GetInstance().SetDefaultProxy(proxy); - } - - - void HttpClient::SetDefaultTimeout(long timeout) - { - GlobalParameters::GetInstance().SetDefaultTimeout(timeout); - } - - - bool HttpClient::Apply(IAnswer& answer) - { - CurlAnswer wrapper(answer, headersToLowerCase_); - return ApplyInternal(wrapper); - } - - - void HttpClient::ApplyAndThrowException(IAnswer& answer) - { - CurlAnswer wrapper(answer, headersToLowerCase_); - - if (!ApplyInternal(wrapper)) - { - ThrowException(GetLastStatus()); - } - } - - - void HttpClient::ApplyAndThrowException(std::string& answerBody) - { - if (!Apply(answerBody)) - { - ThrowException(GetLastStatus()); - } - } - - - void HttpClient::ApplyAndThrowException(Json::Value& answerBody) - { - if (!Apply(answerBody)) - { - ThrowException(GetLastStatus()); - } - } - - - void HttpClient::ApplyAndThrowException(std::string& answerBody, - HttpHeaders& answerHeaders) - { - if (!Apply(answerBody, answerHeaders)) - { - ThrowException(GetLastStatus()); - } - } - - - void HttpClient::ApplyAndThrowException(Json::Value& answerBody, - HttpHeaders& answerHeaders) - { - if (!Apply(answerBody, answerHeaders)) - { - ThrowException(GetLastStatus()); - } - } - - - void HttpClient::SetClientCertificate(const std::string& certificateFile, - const std::string& certificateKeyFile, - const std::string& certificateKeyPassword) - { - if (certificateFile.empty()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (!SystemToolbox::IsRegularFile(certificateFile)) - { - throw OrthancException(ErrorCode_InexistentFile, - "Cannot open certificate file: " + certificateFile); - } - - if (!certificateKeyFile.empty() && - !SystemToolbox::IsRegularFile(certificateKeyFile)) - { - throw OrthancException(ErrorCode_InexistentFile, - "Cannot open key file: " + certificateKeyFile); - } - - clientCertificateFile_ = certificateFile; - clientCertificateKeyFile_ = certificateKeyFile; - clientCertificateKeyPassword_ = certificateKeyPassword; - } - - - void HttpClient::InitializePkcs11(const std::string& module, - const std::string& pin, - bool verbose) - { -#if ORTHANC_ENABLE_PKCS11 == 1 - LOG(INFO) << "Initializing PKCS#11 using " << module - << (pin.empty() ? " (no PIN provided)" : " (PIN is provided)"); - GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose); -#else - throw OrthancException(ErrorCode_InternalError, - "This version of Orthanc is compiled without support for PKCS#11"); -#endif - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpClient.h --- a/Core/HttpClient.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,341 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "Enumerations.h" -#include "OrthancFramework.h" -#include "WebServiceParameters.h" - -#include -#include -#include -#include - -#if !defined(ORTHANC_ENABLE_CURL) -# error The macro ORTHANC_ENABLE_CURL must be defined -#endif - -#if ORTHANC_ENABLE_CURL != 1 -# error Support for curl is disabled, cannot use this file -#endif - -#if !defined(ORTHANC_ENABLE_SSL) -# error The macro ORTHANC_ENABLE_SSL must be defined -#endif - -#if !defined(ORTHANC_ENABLE_PKCS11) -# error The macro ORTHANC_ENABLE_PKCS11 must be defined -#endif - - -namespace Orthanc -{ - class ORTHANC_PUBLIC HttpClient : public boost::noncopyable - { - public: - typedef std::map HttpHeaders; - - class IRequestBody : public boost::noncopyable - { - public: - virtual ~IRequestBody() - { - } - - virtual bool ReadNextChunk(std::string& chunk) = 0; - }; - - class IAnswer : public boost::noncopyable - { - public: - virtual ~IAnswer() - { - } - - virtual void AddHeader(const std::string& key, - const std::string& value) = 0; - - virtual void AddChunk(const void* data, - size_t size) = 0; - }; - - private: - class CurlHeaders; - class CurlRequestBody; - class CurlAnswer; - class DefaultAnswer; - class GlobalParameters; - - struct PImpl; - boost::shared_ptr pimpl_; - - std::string url_; - std::string credentials_; - HttpMethod method_; - HttpStatus lastStatus_; - std::string body_; // This only makes sense for POST and PUT requests - bool isVerbose_; - long timeout_; - std::string proxy_; - bool verifyPeers_; - std::string caCertificates_; - std::string clientCertificateFile_; - std::string clientCertificateKeyFile_; - std::string clientCertificateKeyPassword_; - bool pkcs11Enabled_; - bool headersToLowerCase_; - bool redirectionFollowed_; - - void Setup(); - - void operator= (const HttpClient&); // Assignment forbidden - HttpClient(const HttpClient& base); // Copy forbidden - - bool ApplyInternal(CurlAnswer& answer); - - bool ApplyInternal(std::string& answerBody, - HttpHeaders* answerHeaders); - - bool ApplyInternal(Json::Value& answerBody, - HttpHeaders* answerHeaders); - - public: - HttpClient(); - - HttpClient(const WebServiceParameters& service, - const std::string& uri); - - ~HttpClient(); - - void SetUrl(const char* url) - { - url_ = std::string(url); - } - - void SetUrl(const std::string& url) - { - url_ = url; - } - - const std::string& GetUrl() const - { - return url_; - } - - void SetMethod(HttpMethod method) - { - method_ = method; - } - - HttpMethod GetMethod() const - { - return method_; - } - - void SetTimeout(long seconds) - { - timeout_ = seconds; - } - - long GetTimeout() const - { - return timeout_; - } - - void SetBody(const std::string& data); - - std::string& GetBody() - { - return body_; - } - - const std::string& GetBody() const - { - return body_; - } - - void SetBody(IRequestBody& body); - - void ClearBody(); - - void SetVerbose(bool isVerbose); - - bool IsVerbose() const - { - return isVerbose_; - } - - void AddHeader(const std::string& key, - const std::string& value); - - void ClearHeaders(); - - bool Apply(IAnswer& answer); - - bool Apply(std::string& answerBody) - { - return ApplyInternal(answerBody, NULL); - } - - bool Apply(Json::Value& answerBody) - { - return ApplyInternal(answerBody, NULL); - } - - bool Apply(std::string& answerBody, - HttpHeaders& answerHeaders) - { - return ApplyInternal(answerBody, &answerHeaders); - } - - bool Apply(Json::Value& answerBody, - HttpHeaders& answerHeaders) - { - return ApplyInternal(answerBody, &answerHeaders); - } - - HttpStatus GetLastStatus() const - { - return lastStatus_; - } - - void SetCredentials(const char* username, - const char* password); - - void SetProxy(const std::string& proxy) - { - proxy_ = proxy; - } - - void SetHttpsVerifyPeers(bool verify) - { - verifyPeers_ = verify; - } - - bool IsHttpsVerifyPeers() const - { - return verifyPeers_; - } - - void SetHttpsCACertificates(const std::string& certificates) - { - caCertificates_ = certificates; - } - - const std::string& GetHttpsCACertificates() const - { - return caCertificates_; - } - - void SetClientCertificate(const std::string& certificateFile, - const std::string& certificateKeyFile, - const std::string& certificateKeyPassword); - - void SetPkcs11Enabled(bool enabled) - { - pkcs11Enabled_ = enabled; - } - - bool IsPkcs11Enabled() const - { - return pkcs11Enabled_; - } - - const std::string& GetClientCertificateFile() const - { - return clientCertificateFile_; - } - - const std::string& GetClientCertificateKeyFile() const - { - return clientCertificateKeyFile_; - } - - const std::string& GetClientCertificateKeyPassword() const - { - return clientCertificateKeyPassword_; - } - - void SetConvertHeadersToLowerCase(bool lowerCase) - { - headersToLowerCase_ = lowerCase; - } - - bool IsConvertHeadersToLowerCase() const - { - return headersToLowerCase_; - } - - void SetRedirectionFollowed(bool follow) - { - redirectionFollowed_ = follow; - } - - bool IsRedirectionFollowed() const - { - return redirectionFollowed_; - } - - static void GlobalInitialize(); - - static void GlobalFinalize(); - - static void InitializePkcs11(const std::string& module, - const std::string& pin, - bool verbose); - - static void ConfigureSsl(bool httpsVerifyPeers, - const std::string& httpsCACertificates); - - static void SetDefaultVerbose(bool verbose); - - static void SetDefaultProxy(const std::string& proxy); - - static void SetDefaultTimeout(long timeout); - - void ApplyAndThrowException(IAnswer& answer); - - void ApplyAndThrowException(std::string& answerBody); - - void ApplyAndThrowException(Json::Value& answerBody); - - void ApplyAndThrowException(std::string& answerBody, - HttpHeaders& answerHeaders); - - void ApplyAndThrowException(Json::Value& answerBody, - HttpHeaders& answerHeaders); - - static void ThrowException(HttpStatus status); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/BufferHttpSender.cpp --- a/Core/HttpServer/BufferHttpSender.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - -#include "../PrecompiledHeaders.h" -#include "BufferHttpSender.h" - -#include "../OrthancException.h" - -#include - -namespace Orthanc -{ - BufferHttpSender::BufferHttpSender() : - position_(0), - chunkSize_(0), - currentChunkSize_(0) - { - } - - - bool BufferHttpSender::ReadNextChunk() - { - assert(position_ + currentChunkSize_ <= buffer_.size()); - - position_ += currentChunkSize_; - - if (position_ == buffer_.size()) - { - return false; - } - else - { - currentChunkSize_ = buffer_.size() - position_; - - if (chunkSize_ != 0 && - currentChunkSize_ > chunkSize_) - { - currentChunkSize_ = chunkSize_; - } - - return true; - } - } - - - const char* BufferHttpSender::GetChunkContent() - { - return buffer_.c_str() + position_; - } - - - size_t BufferHttpSender::GetChunkSize() - { - return currentChunkSize_; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/BufferHttpSender.h --- a/Core/HttpServer/BufferHttpSender.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#include "HttpFileSender.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC BufferHttpSender : public HttpFileSender - { - private: - std::string buffer_; - size_t position_; - size_t chunkSize_; - size_t currentChunkSize_; - - public: - BufferHttpSender(); - - std::string& GetBuffer() - { - return buffer_; - } - - const std::string& GetBuffer() const - { - return buffer_; - } - - // This is for test purpose. If "chunkSize" is set to "0" (the - // default), the entire buffer is consumed at once. - void SetChunkSize(size_t chunkSize) - { - chunkSize_ = chunkSize; - } - - - /** - * Implementation of the IHttpStreamAnswer interface. - **/ - - virtual uint64_t GetContentLength() - { - return buffer_.size(); - } - - virtual bool ReadNextChunk(); - - virtual const char* GetChunkContent(); - - virtual size_t GetChunkSize(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/FilesystemHttpHandler.cpp --- a/Core/HttpServer/FilesystemHttpHandler.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "FilesystemHttpHandler.h" - -#include "../OrthancException.h" -#include "../SystemToolbox.h" -#include "FilesystemHttpSender.h" - -#include - - -namespace Orthanc -{ - struct FilesystemHttpHandler::PImpl - { - UriComponents baseUri_; - boost::filesystem::path root_; - }; - - - - static void OutputDirectoryContent(HttpOutput& output, - const IHttpHandler::Arguments& headers, - const UriComponents& uri, - const boost::filesystem::path& p) - { - namespace fs = boost::filesystem; - - std::string s; - s += ""; - s += " "; - s += "

Subdirectories

"; - s += "
    "; - - if (uri.size() > 0) - { - std::string h = Toolbox::FlattenUri(uri) + "/.."; - s += "
  • ..
  • "; - } - - fs::directory_iterator end; - for (fs::directory_iterator it(p) ; it != end; ++it) - { -#if BOOST_HAS_FILESYSTEM_V3 == 1 - std::string f = it->path().filename().string(); -#else - std::string f = it->path().filename(); -#endif - - std::string h = Toolbox::FlattenUri(uri) + "/" + f; - if (fs::is_directory(it->status())) - s += "
  • " + f + "
  • "; - } - - s += "
"; - s += "

Files

"; - s += "
    "; - - for (fs::directory_iterator it(p) ; it != end; ++it) - { -#if BOOST_HAS_FILESYSTEM_V3 == 1 - std::string f = it->path().filename().string(); -#else - std::string f = it->path().filename(); -#endif - - std::string h = Toolbox::FlattenUri(uri) + "/" + f; - if (SystemToolbox::IsRegularFile(it->path().string())) - { - s += "
  • " + f + "
  • "; - } - } - - s += "
"; - s += " "; - s += ""; - - output.SetContentType(MimeType_Html); - output.Answer(s); - } - - - FilesystemHttpHandler::FilesystemHttpHandler(const std::string& baseUri, - const std::string& root) : pimpl_(new PImpl) - { - Toolbox::SplitUriComponents(pimpl_->baseUri_, baseUri); - pimpl_->root_ = root; - listDirectoryContent_ = false; - - namespace fs = boost::filesystem; - if (!fs::exists(pimpl_->root_) || - !fs::is_directory(pimpl_->root_)) - { - throw OrthancException(ErrorCode_DirectoryExpected); - } - } - - - bool FilesystemHttpHandler::Handle( - HttpOutput& output, - RequestOrigin /*origin*/, - const char* /*remoteIp*/, - const char* /*username*/, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers, - const GetArguments& arguments, - const void* /*bodyData*/, - size_t /*bodySize*/) - { - if (!Toolbox::IsChildUri(pimpl_->baseUri_, uri)) - { - // This URI is not served by this handler - return false; - } - - if (method != HttpMethod_Get) - { - output.SendMethodNotAllowed("GET"); - return true; - } - - namespace fs = boost::filesystem; - - fs::path p = pimpl_->root_; - for (size_t i = pimpl_->baseUri_.size(); i < uri.size(); i++) - { - p /= uri[i]; - } - - if (SystemToolbox::IsRegularFile(p.string())) - { - FilesystemHttpSender sender(p); - sender.SetContentType(SystemToolbox::AutodetectMimeType(p.string())); - output.Answer(sender); // TODO COMPRESSION - } - else if (listDirectoryContent_ && - fs::exists(p) && - fs::is_directory(p)) - { - OutputDirectoryContent(output, headers, uri, p); - } - else - { - output.SendStatus(HttpStatus_404_NotFound); - } - - return true; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/FilesystemHttpHandler.h --- a/Core/HttpServer/FilesystemHttpHandler.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IHttpHandler.h" - -#include - -namespace Orthanc -{ - class FilesystemHttpHandler : public IHttpHandler - { - private: - // PImpl idiom to avoid the inclusion of boost::filesystem - // throughout the software - struct PImpl; - boost::shared_ptr pimpl_; - - bool listDirectoryContent_; - - public: - FilesystemHttpHandler(const std::string& baseUri, - const std::string& root); - - virtual bool CreateChunkedRequestReader(std::unique_ptr& target, - RequestOrigin origin, - const char* remoteIp, - const char* username, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers) - { - return false; - } - - virtual bool Handle( - HttpOutput& output, - RequestOrigin origin, - const char* remoteIp, - const char* username, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers, - const GetArguments& arguments, - const void* /*bodyData*/, - size_t /*bodySize*/); - - bool IsListDirectoryContent() const - { - return listDirectoryContent_; - } - - void SetListDirectoryContent(bool enabled) - { - listDirectoryContent_ = enabled; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/FilesystemHttpSender.cpp --- a/Core/HttpServer/FilesystemHttpSender.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - -#include "../PrecompiledHeaders.h" -#include "FilesystemHttpSender.h" - -#include "../OrthancException.h" - -static const size_t CHUNK_SIZE = 64 * 1024; // Use 64KB chunks - -namespace Orthanc -{ - void FilesystemHttpSender::Initialize(const boost::filesystem::path& path) - { - SetContentFilename(path.filename().string()); - file_.open(path.string().c_str(), std::ifstream::binary); - - if (!file_.is_open()) - { - throw OrthancException(ErrorCode_InexistentFile); - } - - file_.seekg(0, file_.end); - size_ = file_.tellg(); - file_.seekg(0, file_.beg); - } - - - bool FilesystemHttpSender::ReadNextChunk() - { - if (chunk_.size() == 0) - { - chunk_.resize(CHUNK_SIZE); - } - - file_.read(&chunk_[0], chunk_.size()); - - if ((file_.flags() & std::istream::failbit) || - file_.gcount() < 0) - { - throw OrthancException(ErrorCode_CorruptedFile); - } - - chunkSize_ = static_cast(file_.gcount()); - - return chunkSize_ > 0; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/FilesystemHttpSender.h --- a/Core/HttpServer/FilesystemHttpSender.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#include "HttpFileSender.h" -#include "BufferHttpSender.h" -#include "../FileStorage/FilesystemStorage.h" - -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC FilesystemHttpSender : public HttpFileSender - { - private: - std::ifstream file_; - uint64_t size_; - std::string chunk_; - size_t chunkSize_; - - void Initialize(const boost::filesystem::path& path); - - public: - explicit FilesystemHttpSender(const std::string& path) - { - Initialize(path); - } - - explicit FilesystemHttpSender(const boost::filesystem::path& path) - { - Initialize(path); - } - - FilesystemHttpSender(const std::string& path, - MimeType contentType) - { - SetContentType(contentType); - Initialize(path); - } - - FilesystemHttpSender(const FilesystemStorage& storage, - const std::string& uuid) - { - Initialize(storage.GetPath(uuid)); - } - - /** - * Implementation of the IHttpStreamAnswer interface. - **/ - - virtual uint64_t GetContentLength() - { - return size_; - } - - virtual bool ReadNextChunk(); - - virtual const char* GetChunkContent() - { - return chunk_.c_str(); - } - - virtual size_t GetChunkSize() - { - return chunkSize_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpContentNegociation.cpp --- a/Core/HttpServer/HttpContentNegociation.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,271 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "HttpContentNegociation.h" - -#include "../Logging.h" -#include "../OrthancException.h" -#include "../Toolbox.h" - -#include - -namespace Orthanc -{ - HttpContentNegociation::Handler::Handler(const std::string& type, - const std::string& subtype, - IHandler& handler) : - type_(type), - subtype_(subtype), - handler_(handler) - { - } - - - bool HttpContentNegociation::Handler::IsMatch(const std::string& type, - const std::string& subtype) const - { - if (type == "*" && subtype == "*") - { - return true; - } - - if (subtype == "*" && type == type_) - { - return true; - } - - return type == type_ && subtype == subtype_; - } - - - struct HttpContentNegociation::Reference : public boost::noncopyable - { - const Handler& handler_; - uint8_t level_; - float quality_; - - Reference(const Handler& handler, - const std::string& type, - const std::string& subtype, - float quality) : - handler_(handler), - quality_(quality) - { - if (type == "*" && subtype == "*") - { - level_ = 0; - } - else if (subtype == "*") - { - level_ = 1; - } - else - { - level_ = 2; - } - } - - bool operator< (const Reference& other) const - { - if (level_ < other.level_) - { - return true; - } - - if (level_ > other.level_) - { - return false; - } - - return quality_ < other.quality_; - } - }; - - - bool HttpContentNegociation::SplitPair(std::string& first /* out */, - std::string& second /* out */, - const std::string& source, - char separator) - { - size_t pos = source.find(separator); - - if (pos == std::string::npos) - { - return false; - } - else - { - first = Toolbox::StripSpaces(source.substr(0, pos)); - second = Toolbox::StripSpaces(source.substr(pos + 1)); - return true; - } - } - - - float HttpContentNegociation::GetQuality(const Tokens& parameters) - { - for (size_t i = 1; i < parameters.size(); i++) - { - std::string key, value; - if (SplitPair(key, value, parameters[i], '=') && - key == "q") - { - float quality; - bool ok = false; - - try - { - quality = boost::lexical_cast(value); - ok = (quality >= 0.0f && quality <= 1.0f); - } - catch (boost::bad_lexical_cast&) - { - } - - if (ok) - { - return quality; - } - else - { - throw OrthancException( - ErrorCode_BadRequest, - "Quality parameter out of range in a HTTP request (must be between 0 and 1): " + value); - } - } - } - - return 1.0f; // Default quality - } - - - void HttpContentNegociation::SelectBestMatch(std::unique_ptr& best, - const Handler& handler, - const std::string& type, - const std::string& subtype, - float quality) - { - std::unique_ptr match(new Reference(handler, type, subtype, quality)); - - if (best.get() == NULL || - *best < *match) - { -#if __cplusplus < 201103L - best.reset(match.release()); -#else - best = std::move(match); -#endif - } - } - - - void HttpContentNegociation::Register(const std::string& mime, - IHandler& handler) - { - std::string type, subtype; - - if (SplitPair(type, subtype, mime, '/') && - type != "*" && - subtype != "*") - { - handlers_.push_back(Handler(type, subtype, handler)); - } - else - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - bool HttpContentNegociation::Apply(const HttpHeaders& headers) - { - HttpHeaders::const_iterator accept = headers.find("accept"); - if (accept != headers.end()) - { - return Apply(accept->second); - } - else - { - return Apply("*/*"); - } - } - - - bool HttpContentNegociation::Apply(const std::string& accept) - { - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 - // https://en.wikipedia.org/wiki/Content_negotiation - // http://www.newmediacampaigns.com/blog/browser-rest-http-accept-headers - - Tokens mediaRanges; - Toolbox::TokenizeString(mediaRanges, accept, ','); - - std::unique_ptr bestMatch; - - for (Tokens::const_iterator it = mediaRanges.begin(); - it != mediaRanges.end(); ++it) - { - Tokens parameters; - Toolbox::TokenizeString(parameters, *it, ';'); - - if (parameters.size() > 0) - { - float quality = GetQuality(parameters); - - std::string type, subtype; - if (SplitPair(type, subtype, parameters[0], '/')) - { - for (Handlers::const_iterator it2 = handlers_.begin(); - it2 != handlers_.end(); ++it2) - { - if (it2->IsMatch(type, subtype)) - { - SelectBestMatch(bestMatch, *it2, type, subtype, quality); - } - } - } - } - } - - if (bestMatch.get() == NULL) // No match was found - { - return false; - } - else - { - bestMatch->handler_.Call(); - return true; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpContentNegociation.h --- a/Core/HttpServer/HttpContentNegociation.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#include "../OrthancFramework.h" -#include "../Compatibility.h" - -#include -#include -#include -#include -#include -#include -#include - - -namespace Orthanc -{ - class ORTHANC_PUBLIC HttpContentNegociation : public boost::noncopyable - { - public: - typedef std::map HttpHeaders; - - class IHandler : public boost::noncopyable - { - public: - virtual ~IHandler() - { - } - - virtual void Handle(const std::string& type, - const std::string& subtype) = 0; - }; - - private: - struct Handler - { - std::string type_; - std::string subtype_; - IHandler& handler_; - - Handler(const std::string& type, - const std::string& subtype, - IHandler& handler); - - bool IsMatch(const std::string& type, - const std::string& subtype) const; - - void Call() const - { - handler_.Handle(type_, subtype_); - } - }; - - - struct Reference; - - typedef std::vector Tokens; - typedef std::list Handlers; - - Handlers handlers_; - - - static bool SplitPair(std::string& first /* out */, - std::string& second /* out */, - const std::string& source, - char separator); - - static float GetQuality(const Tokens& parameters); - - static void SelectBestMatch(std::unique_ptr& best, - const Handler& handler, - const std::string& type, - const std::string& subtype, - float quality); - - public: - void Register(const std::string& mime, - IHandler& handler); - - bool Apply(const HttpHeaders& headers); - - bool Apply(const std::string& accept); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpFileSender.cpp --- a/Core/HttpServer/HttpFileSender.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "HttpFileSender.h" - -#include "../OrthancException.h" -#include "../Toolbox.h" -#include "../SystemToolbox.h" - -#include - -namespace Orthanc -{ - void HttpFileSender::SetContentFilename(const std::string& filename) - { - filename_ = filename; - - if (contentType_.empty()) - { - contentType_ = SystemToolbox::AutodetectMimeType(filename); - } - } - - - bool HttpFileSender::HasContentFilename(std::string& filename) - { - if (filename_.empty()) - { - return false; - } - else - { - filename = filename_; - return true; - } - } - - std::string HttpFileSender::GetContentType() - { - if (contentType_.empty()) - { - return MIME_BINARY; - } - else - { - return contentType_; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpFileSender.h --- a/Core/HttpServer/HttpFileSender.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "HttpOutput.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC HttpFileSender : public IHttpStreamAnswer - { - private: - std::string contentType_; - std::string filename_; - - public: - void SetContentType(MimeType contentType) - { - contentType_ = EnumerationToString(contentType); - } - - void SetContentType(const std::string& contentType) - { - contentType_ = contentType; - } - - const std::string& GetContentType() const - { - return contentType_; - } - - void SetContentFilename(const std::string& filename); - - const std::string& GetContentFilename() const - { - return filename_; - } - - - /** - * Implementation of the IHttpStreamAnswer interface. - **/ - - virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/, - bool /*deflateAllowed*/) - { - return HttpCompression_None; - } - - virtual bool HasContentFilename(std::string& filename); - - virtual std::string GetContentType(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpOutput.cpp --- a/Core/HttpServer/HttpOutput.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,772 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "HttpOutput.h" - -#include "../ChunkedBuffer.h" -#include "../Compression/GzipCompressor.h" -#include "../Compression/ZlibCompressor.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "../Toolbox.h" - -#include -#include -#include -#include - - -#if ORTHANC_ENABLE_CIVETWEB == 1 -# if !defined(CIVETWEB_HAS_DISABLE_KEEP_ALIVE) -# error Macro CIVETWEB_HAS_DISABLE_KEEP_ALIVE must be defined -# endif -#endif - - -namespace Orthanc -{ - HttpOutput::StateMachine::StateMachine(IHttpOutputStream& stream, - bool isKeepAlive) : - stream_(stream), - state_(State_WritingHeader), - status_(HttpStatus_200_Ok), - hasContentLength_(false), - contentPosition_(0), - keepAlive_(isKeepAlive) - { - } - - HttpOutput::StateMachine::~StateMachine() - { - if (state_ != State_Done) - { - //asm volatile ("int3;"); - //LOG(ERROR) << "This HTTP answer does not contain any body"; - } - - if (hasContentLength_ && contentPosition_ != contentLength_) - { - LOG(ERROR) << "This HTTP answer has not sent the proper number of bytes in its body"; - } - } - - - void HttpOutput::StateMachine::SetHttpStatus(HttpStatus status) - { - if (state_ != State_WritingHeader) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - status_ = status; - } - - - void HttpOutput::StateMachine::SetContentLength(uint64_t length) - { - if (state_ != State_WritingHeader) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - hasContentLength_ = true; - contentLength_ = length; - } - - void HttpOutput::StateMachine::SetContentType(const char* contentType) - { - AddHeader("Content-Type", contentType); - } - - void HttpOutput::StateMachine::SetContentFilename(const char* filename) - { - // TODO Escape double quotes - AddHeader("Content-Disposition", "filename=\"" + std::string(filename) + "\""); - } - - void HttpOutput::StateMachine::SetCookie(const std::string& cookie, - const std::string& value) - { - if (state_ != State_WritingHeader) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - // TODO Escape "=" characters - AddHeader("Set-Cookie", cookie + "=" + value); - } - - - void HttpOutput::StateMachine::AddHeader(const std::string& header, - const std::string& value) - { - if (state_ != State_WritingHeader) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - headers_.push_back(header + ": " + value + "\r\n"); - } - - void HttpOutput::StateMachine::ClearHeaders() - { - if (state_ != State_WritingHeader) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - headers_.clear(); - } - - void HttpOutput::StateMachine::SendBody(const void* buffer, size_t length) - { - if (state_ == State_Done) - { - if (length == 0) - { - return; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "Because of keep-alive connections, the entire body must " - "be sent at once or Content-Length must be given"); - } - } - - if (state_ == State_WritingMultipart) - { - throw OrthancException(ErrorCode_InternalError); - } - - if (state_ == State_WritingHeader) - { - // Send the HTTP header before writing the body - - stream_.OnHttpStatusReceived(status_); - - std::string s = "HTTP/1.1 " + - boost::lexical_cast(status_) + - " " + std::string(EnumerationToString(status_)) + - "\r\n"; - - if (keepAlive_) - { - s += "Connection: keep-alive\r\n"; - } - else - { - s += "Connection: close\r\n"; - } - - for (std::list::const_iterator - it = headers_.begin(); it != headers_.end(); ++it) - { - s += *it; - } - - if (status_ != HttpStatus_200_Ok) - { - hasContentLength_ = false; - } - - uint64_t contentLength = (hasContentLength_ ? contentLength_ : length); - s += "Content-Length: " + boost::lexical_cast(contentLength) + "\r\n\r\n"; - - stream_.Send(true, s.c_str(), s.size()); - state_ = State_WritingBody; - } - - if (hasContentLength_ && - contentPosition_ + length > contentLength_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "The body size exceeds what was declared with SetContentSize()"); - } - - if (length > 0) - { - stream_.Send(false, buffer, length); - contentPosition_ += length; - } - - if (!hasContentLength_ || - contentPosition_ == contentLength_) - { - state_ = State_Done; - } - } - - - void HttpOutput::StateMachine::CloseBody() - { - switch (state_) - { - case State_WritingHeader: - SetContentLength(0); - SendBody(NULL, 0); - break; - - case State_WritingBody: - if (!hasContentLength_ || - contentPosition_ == contentLength_) - { - state_ = State_Done; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "The body size has not reached what was declared with SetContentSize()"); - } - - break; - - case State_WritingMultipart: - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "Cannot invoke CloseBody() with multipart outputs"); - - case State_Done: - return; // Ignore - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - - HttpCompression HttpOutput::GetPreferredCompression(size_t bodySize) const - { -#if 0 - // TODO Do not compress small files? - if (bodySize < 512) - { - return HttpCompression_None; - } -#endif - - // Prefer "gzip" over "deflate" if the choice is offered - - if (isGzipAllowed_) - { - return HttpCompression_Gzip; - } - else if (isDeflateAllowed_) - { - return HttpCompression_Deflate; - } - else - { - return HttpCompression_None; - } - } - - - void HttpOutput::SendMethodNotAllowed(const std::string& allowed) - { - stateMachine_.ClearHeaders(); - stateMachine_.SetHttpStatus(HttpStatus_405_MethodNotAllowed); - stateMachine_.AddHeader("Allow", allowed); - stateMachine_.SendBody(NULL, 0); - } - - - void HttpOutput::SendStatus(HttpStatus status, - const char* message, - size_t messageSize) - { - if (status == HttpStatus_301_MovedPermanently || - //status == HttpStatus_401_Unauthorized || - status == HttpStatus_405_MethodNotAllowed) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Please use the dedicated methods to this HTTP status code in HttpOutput"); - } - - stateMachine_.SetHttpStatus(status); - stateMachine_.SendBody(message, messageSize); - } - - - void HttpOutput::Redirect(const std::string& path) - { - stateMachine_.ClearHeaders(); - stateMachine_.SetHttpStatus(HttpStatus_301_MovedPermanently); - stateMachine_.AddHeader("Location", path); - stateMachine_.SendBody(NULL, 0); - } - - - void HttpOutput::SendUnauthorized(const std::string& realm) - { - stateMachine_.ClearHeaders(); - stateMachine_.SetHttpStatus(HttpStatus_401_Unauthorized); - stateMachine_.AddHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\""); - stateMachine_.SendBody(NULL, 0); - } - - - void HttpOutput::Answer(const void* buffer, - size_t length) - { - if (length == 0) - { - AnswerEmpty(); - return; - } - - HttpCompression compression = GetPreferredCompression(length); - - if (compression == HttpCompression_None) - { - stateMachine_.SetContentLength(length); - stateMachine_.SendBody(buffer, length); - return; - } - - std::string compressed, encoding; - - switch (compression) - { - case HttpCompression_Deflate: - { - encoding = "deflate"; - ZlibCompressor compressor; - // Do not prefix the buffer with its uncompressed size, to be compatible with "deflate" - compressor.SetPrefixWithUncompressedSize(false); - compressor.Compress(compressed, buffer, length); - break; - } - - case HttpCompression_Gzip: - { - encoding = "gzip"; - GzipCompressor compressor; - compressor.Compress(compressed, buffer, length); - break; - } - - default: - throw OrthancException(ErrorCode_InternalError); - } - - LOG(TRACE) << "Compressing a HTTP answer using " << encoding; - - // The body is empty, do not use HTTP compression - if (compressed.size() == 0) - { - AnswerEmpty(); - } - else - { - stateMachine_.AddHeader("Content-Encoding", encoding); - stateMachine_.SetContentLength(compressed.size()); - stateMachine_.SendBody(compressed.c_str(), compressed.size()); - } - - stateMachine_.CloseBody(); - } - - - void HttpOutput::Answer(const std::string& str) - { - Answer(str.size() == 0 ? NULL : str.c_str(), str.size()); - } - - - void HttpOutput::AnswerEmpty() - { - stateMachine_.CloseBody(); - } - - - void HttpOutput::StateMachine::CheckHeadersCompatibilityWithMultipart() const - { - for (std::list::const_iterator - it = headers_.begin(); it != headers_.end(); ++it) - { - if (!Toolbox::StartsWith(*it, "Set-Cookie: ")) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "The only headers that can be set in multipart answers " - "are Set-Cookie (here: " + *it + " is set)"); - } - } - } - - - static void PrepareMultipartMainHeader(std::string& boundary, - std::string& contentTypeHeader, - const std::string& subType, - const std::string& contentType) - { - if (subType != "mixed" && - subType != "related") - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - /** - * Fix for issue 54 ("Decide what to do wrt. quoting of multipart - * answers"). The "type" parameter in the "Content-Type" HTTP - * header must be quoted if it contains a forward slash "/". This - * is necessary for DICOMweb compatibility with OsiriX, but breaks - * compatibility with old releases of the client in the Orthanc - * DICOMweb plugin <= 0.3 (releases >= 0.4 work fine). - * - * Full history is available at the following locations: - * - In changeset 2248:69b0f4e8a49b: - * # hg history -v -r 2248 - * - https://bitbucket.org/sjodogne/orthanc/issues/54/ - * - https://groups.google.com/d/msg/orthanc-users/65zhIM5xbKI/TU5Q1_LhAwAJ - **/ - std::string tmp; - if (contentType.find('/') == std::string::npos) - { - // No forward slash in the content type - tmp = contentType; - } - else - { - // Quote the content type because of the forward slash - tmp = "\"" + contentType + "\""; - } - - boundary = Toolbox::GenerateUuid() + "-" + Toolbox::GenerateUuid(); - - /** - * Fix for issue #165: "Encapsulation boundaries must not appear - * within the encapsulations, and must be no longer than 70 - * characters, not counting the two leading hyphens." - * https://tools.ietf.org/html/rfc1521 - * https://bitbucket.org/sjodogne/orthanc/issues/165/ - **/ - if (boundary.size() != 36 + 1 + 36) // one UUID contains 36 characters - { - throw OrthancException(ErrorCode_InternalError); - } - - boundary = boundary.substr(0, 70); - - contentTypeHeader = ("multipart/" + subType + "; type=" + tmp + "; boundary=" + boundary); - } - - - void HttpOutput::StateMachine::StartMultipart(const std::string& subType, - const std::string& contentType) - { - if (state_ != State_WritingHeader) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - if (status_ != HttpStatus_200_Ok) - { - SendBody(NULL, 0); - return; - } - - stream_.OnHttpStatusReceived(status_); - - std::string header = "HTTP/1.1 200 OK\r\n"; - - if (keepAlive_) - { -#if ORTHANC_ENABLE_MONGOOSE == 1 - throw OrthancException(ErrorCode_NotImplemented, - "Multipart answers are not implemented together " - "with keep-alive connections if using Mongoose"); - -#elif ORTHANC_ENABLE_CIVETWEB == 1 -# if CIVETWEB_HAS_DISABLE_KEEP_ALIVE == 1 - // Turn off Keep-Alive for multipart answers - // https://github.com/civetweb/civetweb/issues/727 - stream_.DisableKeepAlive(); - header += "Connection: close\r\n"; -# else - // The function "mg_disable_keep_alive()" is not available, - // let's continue with Keep-Alive. Performance of WADO-RS will - // decrease. - header += "Connection: keep-alive\r\n"; -# endif - -#else -# error Please support your embedded Web server here -#endif - } - else - { - header += "Connection: close\r\n"; - } - - // Possibly add the cookies - CheckHeadersCompatibilityWithMultipart(); - - for (std::list::const_iterator - it = headers_.begin(); it != headers_.end(); ++it) - { - header += *it; - } - - std::string contentTypeHeader; - PrepareMultipartMainHeader(multipartBoundary_, contentTypeHeader, subType, contentType); - multipartContentType_ = contentType; - header += ("Content-Type: " + contentTypeHeader + "\r\n\r\n"); - - stream_.Send(true, header.c_str(), header.size()); - state_ = State_WritingMultipart; - } - - - static void PrepareMultipartItemHeader(std::string& target, - size_t length, - const std::map& headers, - const std::string& boundary, - const std::string& contentType) - { - target = "--" + boundary + "\r\n"; - - bool hasContentType = false; - bool hasContentLength = false; - bool hasMimeVersion = false; - - for (std::map::const_iterator - it = headers.begin(); it != headers.end(); ++it) - { - target += it->first + ": " + it->second + "\r\n"; - - std::string tmp; - Toolbox::ToLowerCase(tmp, it->first); - - if (tmp == "content-type") - { - hasContentType = true; - } - - if (tmp == "content-length") - { - hasContentLength = true; - } - - if (tmp == "mime-version") - { - hasMimeVersion = true; - } - } - - if (!hasContentType) - { - target += "Content-Type: " + contentType + "\r\n"; - } - - if (!hasContentLength) - { - target += "Content-Length: " + boost::lexical_cast(length) + "\r\n"; - } - - if (!hasMimeVersion) - { - target += "MIME-Version: 1.0\r\n\r\n"; - } - } - - - void HttpOutput::StateMachine::SendMultipartItem(const void* item, - size_t length, - const std::map& headers) - { - if (state_ != State_WritingMultipart) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - std::string header; - PrepareMultipartItemHeader(header, length, headers, multipartBoundary_, multipartContentType_); - stream_.Send(false, header.c_str(), header.size()); - - if (length > 0) - { - stream_.Send(false, item, length); - } - - stream_.Send(false, "\r\n", 2); - } - - - void HttpOutput::StateMachine::CloseMultipart() - { - if (state_ != State_WritingMultipart) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - // The two lines below might throw an exception, if the client has - // closed the connection. Such an error is ignored. - try - { - std::string header = "--" + multipartBoundary_ + "--\r\n"; - stream_.Send(false, header.c_str(), header.size()); - } - catch (OrthancException&) - { - } - - state_ = State_Done; - } - - - static void AnswerStreamAsBuffer(HttpOutput& output, - IHttpStreamAnswer& stream) - { - ChunkedBuffer buffer; - - while (stream.ReadNextChunk()) - { - if (stream.GetChunkSize() > 0) - { - buffer.AddChunk(stream.GetChunkContent(), stream.GetChunkSize()); - } - } - - std::string s; - buffer.Flatten(s); - - output.SetContentType(stream.GetContentType()); - - std::string filename; - if (stream.HasContentFilename(filename)) - { - output.SetContentFilename(filename.c_str()); - } - - output.Answer(s); - } - - - void HttpOutput::Answer(IHttpStreamAnswer& stream) - { - HttpCompression compression = stream.SetupHttpCompression(isGzipAllowed_, isDeflateAllowed_); - - switch (compression) - { - case HttpCompression_None: - { - if (isGzipAllowed_ || isDeflateAllowed_) - { - // New in Orthanc 1.5.7: Compress streams without built-in - // compression, if requested by the "Accept-Encoding" HTTP - // header - AnswerStreamAsBuffer(*this, stream); - return; - } - - break; - } - - case HttpCompression_Gzip: - stateMachine_.AddHeader("Content-Encoding", "gzip"); - break; - - case HttpCompression_Deflate: - stateMachine_.AddHeader("Content-Encoding", "deflate"); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - stateMachine_.SetContentLength(stream.GetContentLength()); - - std::string contentType = stream.GetContentType(); - if (contentType.empty()) - { - contentType = MIME_BINARY; - } - - stateMachine_.SetContentType(contentType.c_str()); - - std::string filename; - if (stream.HasContentFilename(filename)) - { - SetContentFilename(filename.c_str()); - } - - while (stream.ReadNextChunk()) - { - stateMachine_.SendBody(stream.GetChunkContent(), - stream.GetChunkSize()); - } - - stateMachine_.CloseBody(); - } - - - void HttpOutput::AnswerMultipartWithoutChunkedTransfer( - const std::string& subType, - const std::string& contentType, - const std::vector& parts, - const std::vector& sizes, - const std::vector*>& headers) - { - if (parts.size() != sizes.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - stateMachine_.CheckHeadersCompatibilityWithMultipart(); - - std::string boundary, contentTypeHeader; - PrepareMultipartMainHeader(boundary, contentTypeHeader, subType, contentType); - SetContentType(contentTypeHeader); - - std::map empty; - - ChunkedBuffer chunked; - for (size_t i = 0; i < parts.size(); i++) - { - std::string partHeader; - PrepareMultipartItemHeader(partHeader, sizes[i], headers[i] == NULL ? empty : *headers[i], - boundary, contentType); - - chunked.AddChunk(partHeader); - chunked.AddChunk(parts[i], sizes[i]); - chunked.AddChunk("\r\n"); - } - - chunked.AddChunk("--" + boundary + "--\r\n"); - - std::string body; - chunked.Flatten(body); - Answer(body); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpOutput.h --- a/Core/HttpServer/HttpOutput.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,250 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" -#include "IHttpOutputStream.h" -#include "IHttpStreamAnswer.h" - -#include -#include -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC HttpOutput : public boost::noncopyable - { - private: - typedef std::list< std::pair > Header; - - class StateMachine : public boost::noncopyable - { - public: - enum State - { - State_WritingHeader, - State_WritingBody, - State_WritingMultipart, - State_Done - }; - - private: - IHttpOutputStream& stream_; - State state_; - - HttpStatus status_; - bool hasContentLength_; - uint64_t contentLength_; - uint64_t contentPosition_; - bool keepAlive_; - std::list headers_; - - std::string multipartBoundary_; - std::string multipartContentType_; - - public: - StateMachine(IHttpOutputStream& stream, - bool isKeepAlive); - - ~StateMachine(); - - void SetHttpStatus(HttpStatus status); - - void SetContentLength(uint64_t length); - - void SetContentType(const char* contentType); - - void SetContentFilename(const char* filename); - - void SetCookie(const std::string& cookie, - const std::string& value); - - void AddHeader(const std::string& header, - const std::string& value); - - void ClearHeaders(); - - void SendBody(const void* buffer, size_t length); - - void StartMultipart(const std::string& subType, - const std::string& contentType); - - void SendMultipartItem(const void* item, - size_t length, - const std::map& headers); - - void CloseMultipart(); - - void CloseBody(); - - State GetState() const - { - return state_; - } - - void CheckHeadersCompatibilityWithMultipart() const; - }; - - StateMachine stateMachine_; - bool isDeflateAllowed_; - bool isGzipAllowed_; - - HttpCompression GetPreferredCompression(size_t bodySize) const; - - public: - HttpOutput(IHttpOutputStream& stream, - bool isKeepAlive) : - stateMachine_(stream, isKeepAlive), - isDeflateAllowed_(false), - isGzipAllowed_(false) - { - } - - void SetDeflateAllowed(bool allowed) - { - isDeflateAllowed_ = allowed; - } - - bool IsDeflateAllowed() const - { - return isDeflateAllowed_; - } - - void SetGzipAllowed(bool allowed) - { - isGzipAllowed_ = allowed; - } - - bool IsGzipAllowed() const - { - return isGzipAllowed_; - } - - void SendStatus(HttpStatus status, - const char* message, - size_t messageSize); - - void SendStatus(HttpStatus status) - { - SendStatus(status, NULL, 0); - } - - void SendStatus(HttpStatus status, - const std::string& message) - { - SendStatus(status, message.c_str(), message.size()); - } - - void SetContentType(MimeType contentType) - { - stateMachine_.SetContentType(EnumerationToString(contentType)); - } - - void SetContentType(const std::string& contentType) - { - stateMachine_.SetContentType(contentType.c_str()); - } - - void SetContentFilename(const char* filename) - { - stateMachine_.SetContentFilename(filename); - } - - void SetCookie(const std::string& cookie, - const std::string& value) - { - stateMachine_.SetCookie(cookie, value); - } - - void AddHeader(const std::string& key, - const std::string& value) - { - stateMachine_.AddHeader(key, value); - } - - void Answer(const void* buffer, - size_t length); - - void Answer(const std::string& str); - - void AnswerEmpty(); - - void SendMethodNotAllowed(const std::string& allowed); - - void Redirect(const std::string& path); - - void SendUnauthorized(const std::string& realm); - - void StartMultipart(const std::string& subType, - const std::string& contentType) - { - stateMachine_.StartMultipart(subType, contentType); - } - - void SendMultipartItem(const void* item, - size_t size, - const std::map& headers) - { - stateMachine_.SendMultipartItem(item, size, headers); - } - - void CloseMultipart() - { - stateMachine_.CloseMultipart(); - } - - bool IsWritingMultipart() const - { - return stateMachine_.GetState() == StateMachine::State_WritingMultipart; - } - - void Answer(IHttpStreamAnswer& stream); - - /** - * This method is a replacement to the combination - * "StartMultipart()" + "SendMultipartItem()". It generates the - * same answer, but it gives a chance to compress the body if - * "Accept-Encoding: gzip" is provided by the client, which is not - * possible in chunked transfers. - **/ - void AnswerMultipartWithoutChunkedTransfer( - const std::string& subType, - const std::string& contentType, - const std::vector& parts, - const std::vector& sizes, - const std::vector*>& headers); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpServer.cpp --- a/Core/HttpServer/HttpServer.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1385 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -// http://en.highscore.de/cpp/boost/stringhandling.html - -#include "../PrecompiledHeaders.h" -#include "HttpServer.h" - -#include "../ChunkedBuffer.h" -#include "../FileBuffer.h" -#include "../Logging.h" -#include "../OrthancException.h" -#include "../TemporaryFile.h" -#include "HttpToolbox.h" - -#if ORTHANC_ENABLE_MONGOOSE == 1 -# include - -#elif ORTHANC_ENABLE_CIVETWEB == 1 -# include -# define MONGOOSE_USE_CALLBACKS 1 -# if !defined(CIVETWEB_HAS_DISABLE_KEEP_ALIVE) -# error Macro CIVETWEB_HAS_DISABLE_KEEP_ALIVE must be defined -# endif - -#else -# error "Either Mongoose or Civetweb must be enabled to compile this file" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if !defined(ORTHANC_ENABLE_SSL) -# error The macro ORTHANC_ENABLE_SSL must be defined -#endif - -#if ORTHANC_ENABLE_SSL == 1 -# include -# include -#endif - -#define ORTHANC_REALM "Orthanc Secure Area" - - -namespace Orthanc -{ - static const char MULTIPART_FORM[] = "multipart/form-data; boundary="; - static unsigned int MULTIPART_FORM_LENGTH = sizeof(MULTIPART_FORM) / sizeof(char) - 1; - - - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - class MongooseOutputStream : public IHttpOutputStream - { - private: - struct mg_connection* connection_; - - public: - MongooseOutputStream(struct mg_connection* connection) : connection_(connection) - { - } - - virtual void Send(bool isHeader, const void* buffer, size_t length) - { - if (length > 0) - { - int status = mg_write(connection_, buffer, length); - if (status != static_cast(length)) - { - // status == 0 when the connection has been closed, -1 on error - throw OrthancException(ErrorCode_NetworkProtocol); - } - } - } - - virtual void OnHttpStatusReceived(HttpStatus status) - { - // Ignore this - } - - virtual void DisableKeepAlive() - { -#if ORTHANC_ENABLE_MONGOOSE == 1 - throw OrthancException(ErrorCode_NotImplemented, - "Only available if using CivetWeb"); - -#elif ORTHANC_ENABLE_CIVETWEB == 1 -# if CIVETWEB_HAS_DISABLE_KEEP_ALIVE == 1 - mg_disable_keep_alive(connection_); -# else -# warning The function "mg_disable_keep_alive()" is not available, DICOMweb might run slowly - throw OrthancException(ErrorCode_NotImplemented, - "Only available if using a patched version of CivetWeb"); -# endif - -#else -# error Please support your embedded Web server here -#endif - } - }; - - - enum PostDataStatus - { - PostDataStatus_Success, - PostDataStatus_NoLength, - PostDataStatus_Pending, - PostDataStatus_Failure - }; - } - - -// TODO Move this to external file - - - class ChunkedFile : public ChunkedBuffer - { - private: - std::string filename_; - - public: - ChunkedFile(const std::string& filename) : - filename_(filename) - { - } - - const std::string& GetFilename() const - { - return filename_; - } - }; - - - - class ChunkStore : public boost::noncopyable - { - private: - typedef std::list Content; - Content content_; - unsigned int numPlaces_; - - boost::mutex mutex_; - std::set discardedFiles_; - - void Clear() - { - for (Content::iterator it = content_.begin(); - it != content_.end(); ++it) - { - delete *it; - } - } - - Content::iterator Find(const std::string& filename) - { - for (Content::iterator it = content_.begin(); - it != content_.end(); ++it) - { - if ((*it)->GetFilename() == filename) - { - return it; - } - } - - return content_.end(); - } - - void Remove(const std::string& filename) - { - Content::iterator it = Find(filename); - if (it != content_.end()) - { - delete *it; - content_.erase(it); - } - } - - public: - ChunkStore() - { - numPlaces_ = 10; - } - - ~ChunkStore() - { - Clear(); - } - - PostDataStatus Store(std::string& completed, - const char* chunkData, - size_t chunkSize, - const std::string& filename, - size_t filesize) - { - boost::mutex::scoped_lock lock(mutex_); - - std::set::iterator wasDiscarded = discardedFiles_.find(filename); - if (wasDiscarded != discardedFiles_.end()) - { - discardedFiles_.erase(wasDiscarded); - return PostDataStatus_Failure; - } - - ChunkedFile* f; - Content::iterator it = Find(filename); - if (it == content_.end()) - { - f = new ChunkedFile(filename); - - // Make some room - if (content_.size() >= numPlaces_) - { - discardedFiles_.insert(content_.front()->GetFilename()); - delete content_.front(); - content_.pop_front(); - } - - content_.push_back(f); - } - else - { - f = *it; - } - - f->AddChunk(chunkData, chunkSize); - - if (f->GetNumBytes() > filesize) - { - Remove(filename); - } - else if (f->GetNumBytes() == filesize) - { - f->Flatten(completed); - Remove(filename); - return PostDataStatus_Success; - } - - return PostDataStatus_Pending; - } - - /*void Print() - { - boost::mutex::scoped_lock lock(mutex_); - - printf("ChunkStore status:\n"); - for (Content::const_iterator i = content_.begin(); - i != content_.end(); i++) - { - printf(" [%s]: %d\n", (*i)->GetFilename().c_str(), (*i)->GetNumBytes()); - } - printf("-----\n"); - }*/ - }; - - - struct HttpServer::PImpl - { - struct mg_context *context_; - ChunkStore chunkStore_; - }; - - - ChunkStore& HttpServer::GetChunkStore() - { - return pimpl_->chunkStore_; - } - - - static PostDataStatus ReadBodyWithContentLength(std::string& body, - struct mg_connection *connection, - const std::string& contentLength) - { - int length; - try - { - length = boost::lexical_cast(contentLength); - } - catch (boost::bad_lexical_cast&) - { - return PostDataStatus_NoLength; - } - - if (length < 0) - { - length = 0; - } - - body.resize(length); - - size_t pos = 0; - while (length > 0) - { - int r = mg_read(connection, &body[pos], length); - if (r <= 0) - { - return PostDataStatus_Failure; - } - - assert(r <= length); - length -= r; - pos += r; - } - - return PostDataStatus_Success; - } - - - static PostDataStatus ReadBodyToString(std::string& body, - struct mg_connection *connection, - const IHttpHandler::Arguments& headers) - { - IHttpHandler::Arguments::const_iterator contentLength = headers.find("content-length"); - - if (contentLength != headers.end()) - { - // "Content-Length" is available - return ReadBodyWithContentLength(body, connection, contentLength->second); - } - else - { - // No Content-Length. Store the individual chunks in a temporary - // file, then read it back into the memory buffer "body" - FileBuffer buffer; - - std::string tmp(1024 * 1024, 0); - - for (;;) - { - int r = mg_read(connection, &tmp[0], tmp.size()); - if (r < 0) - { - return PostDataStatus_Failure; - } - else if (r == 0) - { - break; - } - else - { - buffer.Append(tmp.c_str(), r); - } - } - - buffer.Read(body); - - return PostDataStatus_Success; - } - } - - - static PostDataStatus ReadBodyToStream(IHttpHandler::IChunkedRequestReader& stream, - struct mg_connection *connection, - const IHttpHandler::Arguments& headers) - { - IHttpHandler::Arguments::const_iterator contentLength = headers.find("content-length"); - - if (contentLength != headers.end()) - { - // "Content-Length" is available - std::string body; - PostDataStatus status = ReadBodyWithContentLength(body, connection, contentLength->second); - - if (status == PostDataStatus_Success && - !body.empty()) - { - stream.AddBodyChunk(body.c_str(), body.size()); - } - - return status; - } - else - { - // No Content-Length: This is a chunked transfer. Stream the HTTP connection. - std::string tmp(1024 * 1024, 0); - - for (;;) - { - int r = mg_read(connection, &tmp[0], tmp.size()); - if (r < 0) - { - return PostDataStatus_Failure; - } - else if (r == 0) - { - break; - } - else - { - stream.AddBodyChunk(tmp.c_str(), r); - } - } - - return PostDataStatus_Success; - } - } - - - static PostDataStatus ParseMultipartForm(std::string &completedFile, - struct mg_connection *connection, - const IHttpHandler::Arguments& headers, - const std::string& contentType, - ChunkStore& chunkStore) - { - std::string boundary = "--" + contentType.substr(MULTIPART_FORM_LENGTH); - - std::string body; - PostDataStatus status = ReadBodyToString(body, connection, headers); - - if (status != PostDataStatus_Success) - { - return status; - } - - /*for (IHttpHandler::Arguments::const_iterator i = headers.begin(); i != headers.end(); i++) - { - std::cout << "Header [" << i->first << "] = " << i->second << "\n"; - } - printf("CHUNK\n");*/ - - typedef IHttpHandler::Arguments::const_iterator ArgumentIterator; - - ArgumentIterator requestedWith = headers.find("x-requested-with"); - ArgumentIterator fileName = headers.find("x-file-name"); - ArgumentIterator fileSizeStr = headers.find("x-file-size"); - - if (requestedWith != headers.end() && - requestedWith->second != "XMLHttpRequest") - { - return PostDataStatus_Failure; - } - - size_t fileSize = 0; - if (fileSizeStr != headers.end()) - { - try - { - fileSize = boost::lexical_cast(fileSizeStr->second); - } - catch (boost::bad_lexical_cast&) - { - return PostDataStatus_Failure; - } - } - - typedef boost::find_iterator FindIterator; - typedef boost::iterator_range Range; - - //chunkStore.Print(); - - // TODO - Refactor using class "MultipartStreamReader" - try - { - FindIterator last; - for (FindIterator it = - make_find_iterator(body, boost::first_finder(boundary)); - it!=FindIterator(); - ++it) - { - if (last != FindIterator()) - { - Range part(&last->back(), &it->front()); - Range content = boost::find_first(part, "\r\n\r\n"); - if (/*content != Range()*/!content.empty()) - { - Range c(&content.back() + 1, &it->front() - 2); - size_t chunkSize = c.size(); - - if (chunkSize > 0) - { - const char* chunkData = &c.front(); - - if (fileName == headers.end()) - { - // This file is stored in a single chunk - completedFile.resize(chunkSize); - if (chunkSize > 0) - { - memcpy(&completedFile[0], chunkData, chunkSize); - } - return PostDataStatus_Success; - } - else - { - return chunkStore.Store(completedFile, chunkData, chunkSize, fileName->second, fileSize); - } - } - } - } - - last = it; - } - } - catch (std::length_error&) - { - return PostDataStatus_Failure; - } - - return PostDataStatus_Pending; - } - - - static bool IsAccessGranted(const HttpServer& that, - const IHttpHandler::Arguments& headers) - { - bool granted = false; - - IHttpHandler::Arguments::const_iterator auth = headers.find("authorization"); - if (auth != headers.end()) - { - std::string s = auth->second; - if (s.size() > 6 && - s.substr(0, 6) == "Basic ") - { - std::string b64 = s.substr(6); - granted = that.IsValidBasicHttpAuthentication(b64); - } - } - - return granted; - } - - - static std::string GetAuthenticatedUsername(const IHttpHandler::Arguments& headers) - { - IHttpHandler::Arguments::const_iterator auth = headers.find("authorization"); - - if (auth == headers.end()) - { - return ""; - } - - std::string s = auth->second; - if (s.size() <= 6 || - s.substr(0, 6) != "Basic ") - { - return ""; - } - - std::string b64 = s.substr(6); - std::string decoded; - Toolbox::DecodeBase64(decoded, b64); - size_t semicolons = decoded.find(':'); - - if (semicolons == std::string::npos) - { - // Bad-formatted request - return ""; - } - else - { - return decoded.substr(0, semicolons); - } - } - - - static bool ExtractMethod(HttpMethod& method, - const struct mg_request_info *request, - const IHttpHandler::Arguments& headers, - const IHttpHandler::GetArguments& argumentsGET) - { - std::string overriden; - - // Check whether some PUT/DELETE faking is done - - // 1. Faking with Google's approach - IHttpHandler::Arguments::const_iterator methodOverride = - headers.find("x-http-method-override"); - - if (methodOverride != headers.end()) - { - overriden = methodOverride->second; - } - else if (!strcmp(request->request_method, "GET")) - { - // 2. Faking with Ruby on Rail's approach - // GET /my/resource?_method=delete <=> DELETE /my/resource - for (size_t i = 0; i < argumentsGET.size(); i++) - { - if (argumentsGET[i].first == "_method") - { - overriden = argumentsGET[i].second; - break; - } - } - } - - if (overriden.size() > 0) - { - // A faking has been done within this request - Toolbox::ToUpperCase(overriden); - - LOG(INFO) << "HTTP method faking has been detected for " << overriden; - - if (overriden == "PUT") - { - method = HttpMethod_Put; - return true; - } - else if (overriden == "DELETE") - { - method = HttpMethod_Delete; - return true; - } - else - { - return false; - } - } - - // No PUT/DELETE faking was present - if (!strcmp(request->request_method, "GET")) - { - method = HttpMethod_Get; - } - else if (!strcmp(request->request_method, "POST")) - { - method = HttpMethod_Post; - } - else if (!strcmp(request->request_method, "DELETE")) - { - method = HttpMethod_Delete; - } - else if (!strcmp(request->request_method, "PUT")) - { - method = HttpMethod_Put; - } - else - { - return false; - } - - return true; - } - - - static void ConfigureHttpCompression(HttpOutput& output, - const IHttpHandler::Arguments& headers) - { - // Look if the client wishes HTTP compression - // https://en.wikipedia.org/wiki/HTTP_compression - IHttpHandler::Arguments::const_iterator it = headers.find("accept-encoding"); - if (it != headers.end()) - { - std::vector encodings; - Toolbox::TokenizeString(encodings, it->second, ','); - - for (size_t i = 0; i < encodings.size(); i++) - { - std::string s = Toolbox::StripSpaces(encodings[i]); - - if (s == "deflate") - { - output.SetDeflateAllowed(true); - } - else if (s == "gzip") - { - output.SetGzipAllowed(true); - } - } - } - } - - - static void InternalCallback(HttpOutput& output /* out */, - HttpMethod& method /* out */, - HttpServer& server, - struct mg_connection *connection, - const struct mg_request_info *request) - { - bool localhost; - -#if ORTHANC_ENABLE_MONGOOSE == 1 - static const long LOCALHOST = (127ll << 24) + 1ll; - localhost = (request->remote_ip == LOCALHOST); -#elif ORTHANC_ENABLE_CIVETWEB == 1 - // The "remote_ip" field of "struct mg_request_info" is tagged as - // deprecated in Civetweb, using "remote_addr" instead. - localhost = (std::string(request->remote_addr) == "127.0.0.1"); -#else -# error -#endif - - // Check remote calls - if (!server.IsRemoteAccessAllowed() && - !localhost) - { - output.SendUnauthorized(server.GetRealm()); - return; - } - - - // Extract the HTTP headers - IHttpHandler::Arguments headers; - for (int i = 0; i < request->num_headers; i++) - { - std::string name = request->http_headers[i].name; - std::string value = request->http_headers[i].value; - - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - headers.insert(std::make_pair(name, value)); - VLOG(1) << "HTTP header: [" << name << "]: [" << value << "]"; - } - - if (server.IsHttpCompressionEnabled()) - { - ConfigureHttpCompression(output, headers); - } - - - // Extract the GET arguments - IHttpHandler::GetArguments argumentsGET; - if (!strcmp(request->request_method, "GET")) - { - HttpToolbox::ParseGetArguments(argumentsGET, request->query_string); - } - - - // Compute the HTTP method, taking method faking into consideration - method = HttpMethod_Get; - if (!ExtractMethod(method, request, headers, argumentsGET)) - { - output.SendStatus(HttpStatus_400_BadRequest); - return; - } - - - // Authenticate this connection - if (server.IsAuthenticationEnabled() && - !IsAccessGranted(server, headers)) - { - output.SendUnauthorized(server.GetRealm()); - return; - } - - -#if ORTHANC_ENABLE_MONGOOSE == 1 - // Apply the filter, if it is installed - char remoteIp[24]; - sprintf(remoteIp, "%d.%d.%d.%d", - reinterpret_cast(&request->remote_ip) [3], - reinterpret_cast(&request->remote_ip) [2], - reinterpret_cast(&request->remote_ip) [1], - reinterpret_cast(&request->remote_ip) [0]); - - const char* requestUri = request->uri; - -#elif ORTHANC_ENABLE_CIVETWEB == 1 - const char* remoteIp = request->remote_addr; - const char* requestUri = request->local_uri; -#else -# error -#endif - - if (requestUri == NULL) - { - requestUri = ""; - } - - std::string username = GetAuthenticatedUsername(headers); - - IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); - if (filter != NULL) - { - if (!filter->IsAllowed(method, requestUri, remoteIp, - username.c_str(), headers, argumentsGET)) - { - //output.SendUnauthorized(server.GetRealm()); - output.SendStatus(HttpStatus_403_Forbidden); - return; - } - } - - - // Decompose the URI into its components - UriComponents uri; - try - { - Toolbox::SplitUriComponents(uri, requestUri); - } - catch (OrthancException&) - { - output.SendStatus(HttpStatus_400_BadRequest); - return; - } - - LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); - - - bool found = false; - - // Extract the body of the request for PUT and POST, or process - // the body as a stream - - // TODO Avoid unneccessary memcopy of the body - - std::string body; - if (method == HttpMethod_Post || - method == HttpMethod_Put) - { - PostDataStatus status; - - bool isMultipartForm = false; - - IHttpHandler::Arguments::const_iterator ct = headers.find("content-type"); - if (ct != headers.end() && - ct->second.size() >= MULTIPART_FORM_LENGTH && - !memcmp(ct->second.c_str(), MULTIPART_FORM, MULTIPART_FORM_LENGTH)) - { - /** - * The user uses the "upload" form of Orthanc Explorer, for - * file uploads through a HTML form. - **/ - status = ParseMultipartForm(body, connection, headers, ct->second, server.GetChunkStore()); - isMultipartForm = true; - } - - if (!isMultipartForm) - { - std::unique_ptr stream; - - if (server.HasHandler()) - { - found = server.GetHandler().CreateChunkedRequestReader - (stream, RequestOrigin_RestApi, remoteIp, username.c_str(), method, uri, headers); - } - - if (found) - { - if (stream.get() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - status = ReadBodyToStream(*stream, connection, headers); - - if (status == PostDataStatus_Success) - { - stream->Execute(output); - } - } - else - { - status = ReadBodyToString(body, connection, headers); - } - } - - switch (status) - { - case PostDataStatus_NoLength: - output.SendStatus(HttpStatus_411_LengthRequired); - return; - - case PostDataStatus_Failure: - output.SendStatus(HttpStatus_400_BadRequest); - return; - - case PostDataStatus_Pending: - output.AnswerEmpty(); - return; - - case PostDataStatus_Success: - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - if (!found && - server.HasHandler()) - { - found = server.GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(), - method, uri, headers, argumentsGET, body.c_str(), body.size()); - } - - if (!found) - { - throw OrthancException(ErrorCode_UnknownResource); - } - } - - - static void ProtectedCallback(struct mg_connection *connection, - const struct mg_request_info *request) - { - try - { -#if ORTHANC_ENABLE_MONGOOSE == 1 - void *that = request->user_data; - const char* requestUri = request->uri; -#elif ORTHANC_ENABLE_CIVETWEB == 1 - // https://github.com/civetweb/civetweb/issues/409 - void *that = mg_get_user_data(mg_get_context(connection)); - const char* requestUri = request->local_uri; -#else -# error -#endif - - if (requestUri == NULL) - { - requestUri = ""; - } - - HttpServer* server = reinterpret_cast(that); - - if (server == NULL) - { - MongooseOutputStream stream(connection); - HttpOutput output(stream, false /* assume no keep-alive */); - output.SendStatus(HttpStatus_500_InternalServerError); - return; - } - - MongooseOutputStream stream(connection); - HttpOutput output(stream, server->IsKeepAliveEnabled()); - HttpMethod method = HttpMethod_Get; - - try - { - try - { - InternalCallback(output, method, *server, connection, request); - } - catch (OrthancException&) - { - throw; // Pass the exception to the main handler below - } - // Now convert native exceptions as OrthancException - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_BadParameterType, - "Syntax error in some user-supplied data"); - } - catch (boost::filesystem::filesystem_error& e) - { - throw OrthancException(ErrorCode_InternalError, - "Error while accessing the filesystem: " + e.path1().string()); - } - catch (std::runtime_error&) - { - throw OrthancException(ErrorCode_BadRequest, - "Presumably an error while parsing the JSON body"); - } - catch (std::bad_alloc&) - { - throw OrthancException(ErrorCode_NotEnoughMemory, - "The server hosting Orthanc is running out of memory"); - } - catch (...) - { - throw OrthancException(ErrorCode_InternalError, - "An unhandled exception was generated inside the HTTP server"); - } - } - catch (OrthancException& e) - { - assert(server != NULL); - - // Using this candidate handler results in an exception - try - { - if (server->GetExceptionFormatter() == NULL) - { - LOG(ERROR) << "Exception in the HTTP handler: " << e.What(); - output.SendStatus(e.GetHttpStatus()); - } - else - { - server->GetExceptionFormatter()->Format(output, e, method, requestUri); - } - } - catch (OrthancException&) - { - // An exception here reflects the fact that the status code - // was already set by the HTTP handler. - } - } - } - catch (...) - { - // We should never arrive at this point, where it is even impossible to send an answer - LOG(ERROR) << "Catastrophic error inside the HTTP server, giving up"; - } - } - - -#if MONGOOSE_USE_CALLBACKS == 0 - static void* Callback(enum mg_event event, - struct mg_connection *connection, - const struct mg_request_info *request) - { - if (event == MG_NEW_REQUEST) - { - ProtectedCallback(connection, request); - - // Mark as processed - return (void*) ""; - } - else - { - return NULL; - } - } - -#elif MONGOOSE_USE_CALLBACKS == 1 - static int Callback(struct mg_connection *connection) - { - const struct mg_request_info *request = mg_get_request_info(connection); - - ProtectedCallback(connection, request); - - return 1; // Do not let Mongoose handle the request by itself - } - -#else -# error Please set MONGOOSE_USE_CALLBACKS -#endif - - - - - - bool HttpServer::IsRunning() const - { - return (pimpl_->context_ != NULL); - } - - - HttpServer::HttpServer() : pimpl_(new PImpl) - { - pimpl_->context_ = NULL; - handler_ = NULL; - remoteAllowed_ = false; - authentication_ = false; - ssl_ = false; - port_ = 8000; - filter_ = NULL; - keepAlive_ = false; - httpCompression_ = true; - exceptionFormatter_ = NULL; - realm_ = ORTHANC_REALM; - threadsCount_ = 50; // Default value in mongoose - tcpNoDelay_ = true; - requestTimeout_ = 30; // Default value in mongoose/civetweb (30 seconds) - -#if ORTHANC_ENABLE_MONGOOSE == 1 - LOG(INFO) << "This Orthanc server uses Mongoose as its embedded HTTP server"; -#endif - -#if ORTHANC_ENABLE_CIVETWEB == 1 - LOG(INFO) << "This Orthanc server uses CivetWeb as its embedded HTTP server"; -#endif - -#if ORTHANC_ENABLE_SSL == 1 - // Check for the Heartbleed exploit - // https://en.wikipedia.org/wiki/OpenSSL#Heartbleed_bug - if (OPENSSL_VERSION_NUMBER < 0x1000107fL /* openssl-1.0.1g */ && - OPENSSL_VERSION_NUMBER >= 0x1000100fL /* openssl-1.0.1 */) - { - LOG(WARNING) << "This version of OpenSSL is vulnerable to the Heartbleed exploit"; - } -#endif - } - - - HttpServer::~HttpServer() - { - Stop(); - } - - - void HttpServer::SetPortNumber(uint16_t port) - { - Stop(); - port_ = port; - } - - void HttpServer::Start() - { -#if ORTHANC_ENABLE_MONGOOSE == 1 - LOG(INFO) << "Starting embedded Web server using Mongoose"; -#elif ORTHANC_ENABLE_CIVETWEB == 1 - LOG(INFO) << "Starting embedded Web server using Civetweb"; -#else -# error -#endif - - if (!IsRunning()) - { - std::string port = boost::lexical_cast(port_); - std::string numThreads = boost::lexical_cast(threadsCount_); - std::string requestTimeoutMilliseconds = boost::lexical_cast(requestTimeout_ * 1000); - - if (ssl_) - { - port += "s"; - } - - const char *options[] = { - // Set the TCP port for the HTTP server - "listening_ports", port.c_str(), - - // Optimization reported by Chris Hafey - // https://groups.google.com/d/msg/orthanc-users/CKueKX0pJ9E/_UCbl8T-VjIJ - "enable_keep_alive", (keepAlive_ ? "yes" : "no"), - -#if ORTHANC_ENABLE_CIVETWEB == 1 - // https://github.com/civetweb/civetweb/blob/master/docs/UserManual.md#enable_keep_alive-no - "keep_alive_timeout_ms", (keepAlive_ ? "500" : "0"), -#endif - -#if ORTHANC_ENABLE_CIVETWEB == 1 - // Disable TCP Nagle's algorithm to maximize speed (this - // option is not available in Mongoose). - // https://groups.google.com/d/topic/civetweb/35HBR9seFjU/discussion - // https://eklitzke.org/the-caveats-of-tcp-nodelay - "tcp_nodelay", (tcpNoDelay_ ? "1" : "0"), -#endif - - // Set the number of threads - "num_threads", numThreads.c_str(), - - // Set the timeout for the HTTP server - "request_timeout_ms", requestTimeoutMilliseconds.c_str(), - - // Set the SSL certificate, if any. This must be the last option. - ssl_ ? "ssl_certificate" : NULL, - certificate_.c_str(), - NULL - }; - -#if MONGOOSE_USE_CALLBACKS == 0 - pimpl_->context_ = mg_start(&Callback, this, options); - -#elif MONGOOSE_USE_CALLBACKS == 1 - struct mg_callbacks callbacks; - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.begin_request = Callback; - pimpl_->context_ = mg_start(&callbacks, this, options); - -#else -# error Please set MONGOOSE_USE_CALLBACKS -#endif - - if (!pimpl_->context_) - { - bool isSslError = false; - -#if ORTHANC_ENABLE_SSL == 1 - for (;;) - { - unsigned long code = ERR_get_error(); - if (code == 0) - { - break; - } - else - { - isSslError = true; - char message[1024]; - ERR_error_string_n(code, message, sizeof(message) - 1); - LOG(ERROR) << "OpenSSL error: " << message; - } - } -#endif - - if (isSslError) - { - throw OrthancException(ErrorCode_SslInitialization); - } - else - { - throw OrthancException(ErrorCode_HttpPortInUse, - " (port = " + boost::lexical_cast(port_) + ")"); - } - } - - LOG(WARNING) << "HTTP server listening on port: " << GetPortNumber() - << " (HTTPS encryption is " - << (IsSslEnabled() ? "enabled" : "disabled") - << ", remote access is " - << (IsRemoteAccessAllowed() ? "" : "not ") - << "allowed)"; - } - } - - void HttpServer::Stop() - { - if (IsRunning()) - { - mg_stop(pimpl_->context_); - pimpl_->context_ = NULL; - } - } - - - void HttpServer::ClearUsers() - { - Stop(); - registeredUsers_.clear(); - } - - - void HttpServer::RegisterUser(const char* username, - const char* password) - { - Stop(); - - std::string tag = std::string(username) + ":" + std::string(password); - std::string encoded; - Toolbox::EncodeBase64(encoded, tag); - registeredUsers_.insert(encoded); - } - - void HttpServer::SetSslEnabled(bool enabled) - { - Stop(); - -#if ORTHANC_ENABLE_SSL == 0 - if (enabled) - { - throw OrthancException(ErrorCode_SslDisabled); - } - else - { - ssl_ = false; - } -#else - ssl_ = enabled; -#endif - } - - - void HttpServer::SetKeepAliveEnabled(bool enabled) - { - Stop(); - keepAlive_ = enabled; - LOG(INFO) << "HTTP keep alive is " << (enabled ? "enabled" : "disabled"); - -#if ORTHANC_ENABLE_MONGOOSE == 1 - if (enabled) - { - LOG(WARNING) << "You should disable HTTP keep alive, as you are using Mongoose"; - } -#endif - } - - - void HttpServer::SetAuthenticationEnabled(bool enabled) - { - Stop(); - authentication_ = enabled; - } - - void HttpServer::SetSslCertificate(const char* path) - { - Stop(); - certificate_ = path; - } - - void HttpServer::SetRemoteAccessAllowed(bool allowed) - { - Stop(); - remoteAllowed_ = allowed; - } - - void HttpServer::SetHttpCompressionEnabled(bool enabled) - { - Stop(); - httpCompression_ = enabled; - LOG(WARNING) << "HTTP compression is " << (enabled ? "enabled" : "disabled"); - } - - void HttpServer::SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter) - { - Stop(); - filter_ = &filter; - } - - - void HttpServer::SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter) - { - Stop(); - exceptionFormatter_ = &formatter; - } - - - bool HttpServer::IsValidBasicHttpAuthentication(const std::string& basic) const - { - return registeredUsers_.find(basic) != registeredUsers_.end(); - } - - - void HttpServer::Register(IHttpHandler& handler) - { - Stop(); - handler_ = &handler; - } - - - IHttpHandler& HttpServer::GetHandler() const - { - if (handler_ == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - return *handler_; - } - - - void HttpServer::SetThreadsCount(unsigned int threads) - { - if (threads <= 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - Stop(); - threadsCount_ = threads; - - LOG(INFO) << "The embedded HTTP server will use " << threads << " threads"; - } - - - void HttpServer::SetTcpNoDelay(bool tcpNoDelay) - { - Stop(); - tcpNoDelay_ = tcpNoDelay; - LOG(INFO) << "TCP_NODELAY for the HTTP sockets is set to " - << (tcpNoDelay ? "true" : "false"); - } - - - void HttpServer::SetRequestTimeout(unsigned int seconds) - { - if (seconds <= 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, - "Request timeout must be a stricly positive integer"); - } - - Stop(); - requestTimeout_ = seconds; - LOG(INFO) << "Request timeout in the HTTP server is set to " << seconds << " seconds"; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpServer.h --- a/Core/HttpServer/HttpServer.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,230 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -// To have ORTHANC_ENABLE_xxx defined if using the shared library -#include "../OrthancFramework.h" - -#if !defined(ORTHANC_ENABLE_MONGOOSE) -# error Macro ORTHANC_ENABLE_MONGOOSE must be defined to include this file -#endif - -#if !defined(ORTHANC_ENABLE_CIVETWEB) -# error Macro ORTHANC_ENABLE_CIVETWEB must be defined to include this file -#endif - -#if (ORTHANC_ENABLE_MONGOOSE == 0 && \ - ORTHANC_ENABLE_CIVETWEB == 0) -# error Either ORTHANC_ENABLE_MONGOOSE or ORTHANC_ENABLE_CIVETWEB must be set to 1 -#endif - - -#include "IIncomingHttpRequestFilter.h" - -#include -#include -#include -#include -#include - -namespace Orthanc -{ - class ChunkStore; - class OrthancException; - - class IHttpExceptionFormatter : public boost::noncopyable - { - public: - virtual ~IHttpExceptionFormatter() - { - } - - virtual void Format(HttpOutput& output, - const OrthancException& exception, - HttpMethod method, - const char* uri) = 0; - }; - - - class ORTHANC_PUBLIC HttpServer : public boost::noncopyable - { - private: - // http://stackoverflow.com/questions/311166/stdauto-ptr-or-boostshared-ptr-for-pimpl-idiom - struct PImpl; - boost::shared_ptr pimpl_; - - IHttpHandler *handler_; - - typedef std::set RegisteredUsers; - RegisteredUsers registeredUsers_; - - bool remoteAllowed_; - bool authentication_; - bool ssl_; - std::string certificate_; - uint16_t port_; - IIncomingHttpRequestFilter* filter_; - bool keepAlive_; - bool httpCompression_; - IHttpExceptionFormatter* exceptionFormatter_; - std::string realm_; - unsigned int threadsCount_; - bool tcpNoDelay_; - unsigned int requestTimeout_; // In seconds - - bool IsRunning() const; - - public: - HttpServer(); - - ~HttpServer(); - - void SetPortNumber(uint16_t port); - - uint16_t GetPortNumber() const - { - return port_; - } - - void Start(); - - void Stop(); - - void ClearUsers(); - - void RegisterUser(const char* username, - const char* password); - - bool IsAuthenticationEnabled() const - { - return authentication_; - } - - void SetAuthenticationEnabled(bool enabled); - - bool IsSslEnabled() const - { - return ssl_; - } - - void SetSslEnabled(bool enabled); - - bool IsKeepAliveEnabled() const - { - return keepAlive_; - } - - void SetKeepAliveEnabled(bool enabled); - - const std::string& GetSslCertificate() const - { - return certificate_; - } - - void SetSslCertificate(const char* path); - - bool IsRemoteAccessAllowed() const - { - return remoteAllowed_; - } - - void SetRemoteAccessAllowed(bool allowed); - - bool IsHttpCompressionEnabled() const - { - return httpCompression_;; - } - - void SetHttpCompressionEnabled(bool enabled); - - IIncomingHttpRequestFilter* GetIncomingHttpRequestFilter() const - { - return filter_; - } - - void SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter); - - ChunkStore& GetChunkStore(); - - bool IsValidBasicHttpAuthentication(const std::string& basic) const; - - void Register(IHttpHandler& handler); - - bool HasHandler() const - { - return handler_ != NULL; - } - - IHttpHandler& GetHandler() const; - - void SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter); - - IHttpExceptionFormatter* GetExceptionFormatter() - { - return exceptionFormatter_; - } - - const std::string& GetRealm() const - { - return realm_; - } - - void SetRealm(const std::string& realm) - { - realm_ = realm; - } - - void SetThreadsCount(unsigned int threads); - - unsigned int GetThreadsCount() const - { - return threadsCount_; - } - - // New in Orthanc 1.5.2, not available for Mongoose - void SetTcpNoDelay(bool tcpNoDelay); - - bool IsTcpNoDelay() const - { - return tcpNoDelay_; - } - - void SetRequestTimeout(unsigned int seconds); - - unsigned int GetRequestTimeout() const - { - return requestTimeout_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpStreamTranscoder.cpp --- a/Core/HttpServer/HttpStreamTranscoder.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,251 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "HttpStreamTranscoder.h" - -#include "../OrthancException.h" -#include "../Compression/ZlibCompressor.h" - -#include // For memcpy() -#include - -#include - -namespace Orthanc -{ - void HttpStreamTranscoder::ReadSource(std::string& buffer) - { - if (source_.SetupHttpCompression(false, false) != HttpCompression_None) - { - throw OrthancException(ErrorCode_InternalError); - } - - uint64_t size = source_.GetContentLength(); - if (static_cast(static_cast(size)) != size) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - buffer.resize(static_cast(size)); - size_t offset = 0; - - while (source_.ReadNextChunk()) - { - size_t chunkSize = static_cast(source_.GetChunkSize()); - memcpy(&buffer[offset], source_.GetChunkContent(), chunkSize); - offset += chunkSize; - } - - if (offset != size) - { - throw OrthancException(ErrorCode_InternalError); - } - } - - - HttpCompression HttpStreamTranscoder::SetupZlibCompression(bool deflateAllowed) - { - uint64_t size = source_.GetContentLength(); - - if (size == 0) - { - return HttpCompression_None; - } - - if (size < sizeof(uint64_t)) - { - throw OrthancException(ErrorCode_CorruptedFile); - } - - if (deflateAllowed) - { - bytesToSkip_ = sizeof(uint64_t); - - return HttpCompression_Deflate; - } - else - { - // TODO Use stream-based zlib decoding to reduce memory usage - std::string compressed; - ReadSource(compressed); - - uncompressed_.reset(new BufferHttpSender); - - ZlibCompressor compressor; - IBufferCompressor::Uncompress(uncompressed_->GetBuffer(), compressor, compressed); - - return HttpCompression_None; - } - } - - - HttpCompression HttpStreamTranscoder::SetupHttpCompression(bool gzipAllowed, - bool deflateAllowed) - { - if (ready_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - ready_ = true; - - switch (sourceCompression_) - { - case CompressionType_None: - return HttpCompression_None; - - case CompressionType_ZlibWithSize: - return SetupZlibCompression(deflateAllowed); - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - uint64_t HttpStreamTranscoder::GetContentLength() - { - if (!ready_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - if (uncompressed_.get() != NULL) - { - return uncompressed_->GetContentLength(); - } - else - { - uint64_t length = source_.GetContentLength(); - if (length < bytesToSkip_) - { - throw OrthancException(ErrorCode_InternalError); - } - - return length - bytesToSkip_; - } - } - - - bool HttpStreamTranscoder::ReadNextChunk() - { - if (!ready_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - if (uncompressed_.get() != NULL) - { - return uncompressed_->ReadNextChunk(); - } - - assert(skipped_ <= bytesToSkip_); - if (skipped_ == bytesToSkip_) - { - // We have already skipped the first bytes of the stream - currentChunkOffset_ = 0; - return source_.ReadNextChunk(); - } - - // This condition can only be true on the first call to "ReadNextChunk()" - for (;;) - { - assert(skipped_ < bytesToSkip_); - - bool ok = source_.ReadNextChunk(); - if (!ok) - { - throw OrthancException(ErrorCode_CorruptedFile); - } - - size_t remaining = static_cast(bytesToSkip_ - skipped_); - size_t s = source_.GetChunkSize(); - - if (s < remaining) - { - skipped_ += s; - } - else if (s == remaining) - { - // We have skipped enough bytes, but we must read a new chunk - currentChunkOffset_ = 0; - skipped_ = bytesToSkip_; - return source_.ReadNextChunk(); - } - else - { - // We have skipped enough bytes, and we have enough data in the current chunk - assert(s > remaining); - currentChunkOffset_ = remaining; - skipped_ = bytesToSkip_; - return true; - } - } - } - - - const char* HttpStreamTranscoder::GetChunkContent() - { - if (!ready_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - if (uncompressed_.get() != NULL) - { - return uncompressed_->GetChunkContent(); - } - else - { - return source_.GetChunkContent() + currentChunkOffset_; - } - } - - size_t HttpStreamTranscoder::GetChunkSize() - { - if (!ready_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - if (uncompressed_.get() != NULL) - { - return uncompressed_->GetChunkSize(); - } - else - { - return static_cast(source_.GetChunkSize() - currentChunkOffset_); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpStreamTranscoder.h --- a/Core/HttpServer/HttpStreamTranscoder.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "BufferHttpSender.h" - -#include "../Compatibility.h" - -#include // For std::unique_ptr - -namespace Orthanc -{ - class ORTHANC_PUBLIC HttpStreamTranscoder : public IHttpStreamAnswer - { - private: - IHttpStreamAnswer& source_; - CompressionType sourceCompression_; - uint64_t bytesToSkip_; - uint64_t skipped_; - uint64_t currentChunkOffset_; - bool ready_; - - std::unique_ptr uncompressed_; - - void ReadSource(std::string& buffer); - - HttpCompression SetupZlibCompression(bool deflateAllowed); - - public: - HttpStreamTranscoder(IHttpStreamAnswer& source, - CompressionType compression) : - source_(source), - sourceCompression_(compression), - bytesToSkip_(0), - skipped_(0), - currentChunkOffset_(0), - ready_(false) - { - } - - // This is the first method to be called - virtual HttpCompression SetupHttpCompression(bool gzipAllowed, - bool deflateAllowed); - - virtual bool HasContentFilename(std::string& filename) - { - return source_.HasContentFilename(filename); - } - - virtual std::string GetContentType() - { - return source_.GetContentType(); - } - - virtual uint64_t GetContentLength(); - - virtual bool ReadNextChunk(); - - virtual const char* GetChunkContent(); - - virtual size_t GetChunkSize(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpToolbox.cpp --- a/Core/HttpServer/HttpToolbox.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,298 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "HttpToolbox.h" - -#include -#include -#include - -#include "HttpOutput.h" -#include "StringHttpOutput.h" - - -static const char* LOCALHOST = "127.0.0.1"; - - - -namespace Orthanc -{ - static void SplitGETNameValue(IHttpHandler::GetArguments& result, - const char* start, - const char* end) - { - std::string name, value; - - const char* equal = strchr(start, '='); - if (equal == NULL || equal >= end) - { - name = std::string(start, end - start); - //value = ""; - } - else - { - name = std::string(start, equal - start); - value = std::string(equal + 1, end); - } - - Toolbox::UrlDecode(name); - Toolbox::UrlDecode(value); - - result.push_back(std::make_pair(name, value)); - } - - - void HttpToolbox::ParseGetArguments(IHttpHandler::GetArguments& result, - const char* query) - { - const char* pos = query; - - while (pos != NULL) - { - const char* ampersand = strchr(pos, '&'); - if (ampersand) - { - SplitGETNameValue(result, pos, ampersand); - pos = ampersand + 1; - } - else - { - // No more ampersand, this is the last argument - SplitGETNameValue(result, pos, pos + strlen(pos)); - pos = NULL; - } - } - } - - - void HttpToolbox::ParseGetQuery(UriComponents& uri, - IHttpHandler::GetArguments& getArguments, - const char* query) - { - const char *questionMark = ::strchr(query, '?'); - if (questionMark == NULL) - { - // No question mark in the string - Toolbox::SplitUriComponents(uri, query); - getArguments.clear(); - } - else - { - Toolbox::SplitUriComponents(uri, std::string(query, questionMark)); - HttpToolbox::ParseGetArguments(getArguments, questionMark + 1); - } - } - - - std::string HttpToolbox::GetArgument(const IHttpHandler::Arguments& getArguments, - const std::string& name, - const std::string& defaultValue) - { - IHttpHandler::Arguments::const_iterator it = getArguments.find(name); - if (it == getArguments.end()) - { - return defaultValue; - } - else - { - return it->second; - } - } - - - std::string HttpToolbox::GetArgument(const IHttpHandler::GetArguments& getArguments, - const std::string& name, - const std::string& defaultValue) - { - for (size_t i = 0; i < getArguments.size(); i++) - { - if (getArguments[i].first == name) - { - return getArguments[i].second; - } - } - - return defaultValue; - } - - - - void HttpToolbox::ParseCookies(IHttpHandler::Arguments& result, - const IHttpHandler::Arguments& httpHeaders) - { - result.clear(); - - IHttpHandler::Arguments::const_iterator it = httpHeaders.find("cookie"); - if (it != httpHeaders.end()) - { - const std::string& cookies = it->second; - - size_t pos = 0; - while (pos != std::string::npos) - { - size_t nextSemicolon = cookies.find(";", pos); - std::string cookie; - - if (nextSemicolon == std::string::npos) - { - cookie = cookies.substr(pos); - pos = std::string::npos; - } - else - { - cookie = cookies.substr(pos, nextSemicolon - pos); - pos = nextSemicolon + 1; - } - - size_t equal = cookie.find("="); - if (equal != std::string::npos) - { - std::string name = Toolbox::StripSpaces(cookie.substr(0, equal)); - std::string value = Toolbox::StripSpaces(cookie.substr(equal + 1)); - result[name] = value; - } - } - } - } - - - void HttpToolbox::CompileGetArguments(IHttpHandler::Arguments& compiled, - const IHttpHandler::GetArguments& source) - { - compiled.clear(); - - for (size_t i = 0; i < source.size(); i++) - { - compiled[source[i].first] = source[i].second; - } - } - - - bool HttpToolbox::SimpleGet(std::string& result, - IHttpHandler& handler, - RequestOrigin origin, - const std::string& uri, - const IHttpHandler::Arguments& httpHeaders) - { - UriComponents curi; - IHttpHandler::GetArguments getArguments; - ParseGetQuery(curi, getArguments, uri.c_str()); - - StringHttpOutput stream; - HttpOutput http(stream, false /* no keep alive */); - - if (handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Get, curi, - httpHeaders, getArguments, NULL /* no body for GET */, 0)) - { - stream.GetOutput(result); - return true; - } - else - { - return false; - } - } - - - static bool SimplePostOrPut(std::string& result, - IHttpHandler& handler, - RequestOrigin origin, - HttpMethod method, - const std::string& uri, - const void* bodyData, - size_t bodySize, - const IHttpHandler::Arguments& httpHeaders) - { - IHttpHandler::GetArguments getArguments; // No GET argument for POST/PUT - - UriComponents curi; - Toolbox::SplitUriComponents(curi, uri); - - StringHttpOutput stream; - HttpOutput http(stream, false /* no keep alive */); - - if (handler.Handle(http, origin, LOCALHOST, "", method, curi, - httpHeaders, getArguments, bodyData, bodySize)) - { - stream.GetOutput(result); - return true; - } - else - { - return false; - } - } - - - bool HttpToolbox::SimplePost(std::string& result, - IHttpHandler& handler, - RequestOrigin origin, - const std::string& uri, - const void* bodyData, - size_t bodySize, - const IHttpHandler::Arguments& httpHeaders) - { - return SimplePostOrPut(result, handler, origin, HttpMethod_Post, uri, bodyData, bodySize, httpHeaders); - } - - - bool HttpToolbox::SimplePut(std::string& result, - IHttpHandler& handler, - RequestOrigin origin, - const std::string& uri, - const void* bodyData, - size_t bodySize, - const IHttpHandler::Arguments& httpHeaders) - { - return SimplePostOrPut(result, handler, origin, HttpMethod_Put, uri, bodyData, bodySize, httpHeaders); - } - - - bool HttpToolbox::SimpleDelete(IHttpHandler& handler, - RequestOrigin origin, - const std::string& uri, - const IHttpHandler::Arguments& httpHeaders) - { - UriComponents curi; - Toolbox::SplitUriComponents(curi, uri); - - IHttpHandler::GetArguments getArguments; // No GET argument for DELETE - - StringHttpOutput stream; - HttpOutput http(stream, false /* no keep alive */); - - return handler.Handle(http, origin, LOCALHOST, "", HttpMethod_Delete, curi, - httpHeaders, getArguments, NULL /* no body for DELETE */, 0); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/HttpToolbox.h --- a/Core/HttpServer/HttpToolbox.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../OrthancFramework.h" -#include "IHttpHandler.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC HttpToolbox : public boost::noncopyable - { - public: - static void ParseGetArguments(IHttpHandler::GetArguments& result, - const char* query); - - static void ParseGetQuery(UriComponents& uri, - IHttpHandler::GetArguments& getArguments, - const char* query); - - static std::string GetArgument(const IHttpHandler::Arguments& getArguments, - const std::string& name, - const std::string& defaultValue); - - static std::string GetArgument(const IHttpHandler::GetArguments& getArguments, - const std::string& name, - const std::string& defaultValue); - - static void ParseCookies(IHttpHandler::Arguments& result, - const IHttpHandler::Arguments& httpHeaders); - - static void CompileGetArguments(IHttpHandler::Arguments& compiled, - const IHttpHandler::GetArguments& source); - - static bool SimpleGet(std::string& result, - IHttpHandler& handler, - RequestOrigin origin, - const std::string& uri, - const IHttpHandler::Arguments& httpHeaders); - - static bool SimplePost(std::string& result, - IHttpHandler& handler, - RequestOrigin origin, - const std::string& uri, - const void* bodyData, - size_t bodySize, - const IHttpHandler::Arguments& httpHeaders); - - static bool SimplePut(std::string& result, - IHttpHandler& handler, - RequestOrigin origin, - const std::string& uri, - const void* bodyData, - size_t bodySize, - const IHttpHandler::Arguments& httpHeaders); - - static bool SimpleDelete(IHttpHandler& handler, - RequestOrigin origin, - const std::string& uri, - const IHttpHandler::Arguments& httpHeaders); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/IHttpHandler.h --- a/Core/HttpServer/IHttpHandler.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Compatibility.h" -#include "../Toolbox.h" -#include "HttpOutput.h" - -#include -#include -#include -#include -#include - -namespace Orthanc -{ - class IHttpHandler : public boost::noncopyable - { - public: - typedef std::map Arguments; - typedef std::vector< std::pair > GetArguments; - - - class IChunkedRequestReader : public boost::noncopyable - { - public: - virtual ~IChunkedRequestReader() - { - } - - virtual void AddBodyChunk(const void* data, - size_t size) = 0; - - virtual void Execute(HttpOutput& output) = 0; - }; - - - virtual ~IHttpHandler() - { - } - - /** - * This function allows one to deal with chunked transfers (new in - * Orthanc 1.5.7). It is only called if "method" is POST or PUT. - **/ - virtual bool CreateChunkedRequestReader(std::unique_ptr& target, - RequestOrigin origin, - const char* remoteIp, - const char* username, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers) = 0; - - virtual bool Handle(HttpOutput& output, - RequestOrigin origin, - const char* remoteIp, - const char* username, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers, - const GetArguments& getArguments, - const void* bodyData, - size_t bodySize) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/IHttpOutputStream.h --- a/Core/HttpServer/IHttpOutputStream.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include -#include - -namespace Orthanc -{ - class IHttpOutputStream : public boost::noncopyable - { - public: - virtual ~IHttpOutputStream() - { - } - - virtual void OnHttpStatusReceived(HttpStatus status) = 0; - - virtual void Send(bool isHeader, const void* buffer, size_t length) = 0; - - // Disable HTTP keep alive for this single HTTP connection. Must - // be called before sending the "HTTP/1.1 200 OK" header. - virtual void DisableKeepAlive() = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/IHttpStreamAnswer.h --- a/Core/HttpServer/IHttpStreamAnswer.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include -#include -#include - -namespace Orthanc -{ - class IHttpStreamAnswer : public boost::noncopyable - { - public: - virtual ~IHttpStreamAnswer() - { - } - - // This is the first method to be called - virtual HttpCompression SetupHttpCompression(bool gzipAllowed, - bool deflateAllowed) = 0; - - virtual bool HasContentFilename(std::string& filename) = 0; - - virtual std::string GetContentType() = 0; - - virtual uint64_t GetContentLength() = 0; - - virtual bool ReadNextChunk() = 0; - - virtual const char* GetChunkContent() = 0; - - virtual size_t GetChunkSize() = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/IIncomingHttpRequestFilter.h --- a/Core/HttpServer/IIncomingHttpRequestFilter.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IHttpHandler.h" - -namespace Orthanc -{ - class IIncomingHttpRequestFilter : public boost::noncopyable - { - public: - virtual ~IIncomingHttpRequestFilter() - { - } - - virtual bool IsAllowed(HttpMethod method, - const char* uri, - const char* ip, - const char* username, - const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::GetArguments& getArguments) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/MultipartStreamReader.cpp --- a/Core/HttpServer/MultipartStreamReader.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,357 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "MultipartStreamReader.h" - -#include "../OrthancException.h" -#include "../Toolbox.h" - -#include - -#if defined(_MSC_VER) -# include // Definition of ssize_t -#endif - -namespace Orthanc -{ - static void ParseHeaders(MultipartStreamReader::HttpHeaders& headers, - StringMatcher::Iterator start, - StringMatcher::Iterator end) - { - std::string tmp(start, end); - - std::vector lines; - Toolbox::TokenizeString(lines, tmp, '\n'); - - headers.clear(); - - for (size_t i = 0; i < lines.size(); i++) - { - size_t separator = lines[i].find(':'); - if (separator != std::string::npos) - { - std::string key = Toolbox::StripSpaces(lines[i].substr(0, separator)); - std::string value = Toolbox::StripSpaces(lines[i].substr(separator + 1)); - - Toolbox::ToLowerCase(key); - headers[key] = value; - } - } - } - - - static bool LookupHeaderSizeValue(size_t& target, - const MultipartStreamReader::HttpHeaders& headers, - const std::string& key) - { - MultipartStreamReader::HttpHeaders::const_iterator it = headers.find(key); - if (it == headers.end()) - { - return false; - } - else - { - int64_t value; - - try - { - value = boost::lexical_cast(it->second); - } - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (value < 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - target = static_cast(value); - return true; - } - } - } - - - void MultipartStreamReader::ParseStream() - { - if (handler_ == NULL || - state_ == State_Done) - { - return; - } - - std::string corpus; - buffer_.Flatten(corpus); - - StringMatcher::Iterator current = corpus.begin(); - StringMatcher::Iterator corpusEnd = corpus.end(); - - if (state_ == State_UnusedArea) - { - /** - * "Before the first boundary is an area that is ignored by - * MIME-compliant clients. This area is generally used to put - * a message to users of old non-MIME clients." - * https://en.wikipedia.org/wiki/MIME#Multipart_messages - **/ - - if (boundaryMatcher_.Apply(current, corpusEnd)) - { - current = boundaryMatcher_.GetMatchBegin(); - state_ = State_Content; - } - else - { - // We have not seen the end of the unused area yet - std::string reminder(current, corpusEnd); - buffer_.AddChunkDestructive(reminder); - return; - } - } - - for (;;) - { - size_t patternSize = boundaryMatcher_.GetPattern().size(); - size_t remainingSize = std::distance(current, corpusEnd); - if (remainingSize < patternSize + 2) - { - break; // Not enough data available - } - - std::string boundary(current, current + patternSize + 2); - if (boundary == boundaryMatcher_.GetPattern() + "--") - { - state_ = State_Done; - return; - } - - if (boundary != boundaryMatcher_.GetPattern() + "\r\n") - { - throw OrthancException(ErrorCode_NetworkProtocol, - "Garbage between two items in a multipart stream"); - } - - StringMatcher::Iterator start = current + patternSize + 2; - - if (!headersMatcher_.Apply(start, corpusEnd)) - { - break; // Not enough data available - } - - HttpHeaders headers; - ParseHeaders(headers, start, headersMatcher_.GetMatchBegin()); - - size_t contentLength = 0; - if (!LookupHeaderSizeValue(contentLength, headers, "content-length")) - { - if (boundaryMatcher_.Apply(headersMatcher_.GetMatchEnd(), corpusEnd)) - { - size_t d = std::distance(headersMatcher_.GetMatchEnd(), boundaryMatcher_.GetMatchBegin()); - if (d <= 1) - { - throw OrthancException(ErrorCode_NetworkProtocol); - } - else - { - contentLength = d - 2; - } - } - else - { - break; // Not enough data available to have a full part - } - } - - // Explicit conversion to avoid warning about signed vs. unsigned comparison - std::iterator_traits::difference_type d = contentLength + 2; - if (d > std::distance(headersMatcher_.GetMatchEnd(), corpusEnd)) - { - break; // Not enough data available to have a full part - } - - const char* p = headersMatcher_.GetPointerEnd() + contentLength; - if (p[0] != '\r' || - p[1] != '\n') - { - throw OrthancException(ErrorCode_NetworkProtocol, - "No endline at the end of a part"); - } - - handler_->HandlePart(headers, headersMatcher_.GetPointerEnd(), contentLength); - current = headersMatcher_.GetMatchEnd() + contentLength + 2; - } - - if (current != corpusEnd) - { - std::string reminder(current, corpusEnd); - buffer_.AddChunkDestructive(reminder); - } - } - - - MultipartStreamReader::MultipartStreamReader(const std::string& boundary) : - state_(State_UnusedArea), - handler_(NULL), - headersMatcher_("\r\n\r\n"), - boundaryMatcher_("--" + boundary), - blockSize_(10 * 1024 * 1024) - { - } - - - void MultipartStreamReader::SetBlockSize(size_t size) - { - if (size == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - blockSize_ = size; - } - } - - - void MultipartStreamReader::AddChunk(const void* chunk, - size_t size) - { - if (state_ != State_Done && - size != 0) - { - size_t oldSize = buffer_.GetNumBytes(); - - buffer_.AddChunk(chunk, size); - - if (oldSize / blockSize_ != buffer_.GetNumBytes() / blockSize_) - { - ParseStream(); - } - } - } - - - void MultipartStreamReader::AddChunk(const std::string& chunk) - { - if (!chunk.empty()) - { - AddChunk(chunk.c_str(), chunk.size()); - } - } - - - void MultipartStreamReader::CloseStream() - { - if (buffer_.GetNumBytes() != 0) - { - ParseStream(); - } - } - - - bool MultipartStreamReader::GetMainContentType(std::string& contentType, - const HttpHeaders& headers) - { - HttpHeaders::const_iterator it = headers.find("content-type"); - - if (it == headers.end()) - { - return false; - } - else - { - contentType = it->second; - return true; - } - } - - - bool MultipartStreamReader::ParseMultipartContentType(std::string& contentType, - std::string& subType, - std::string& boundary, - const std::string& contentTypeHeader) - { - std::vector tokens; - Orthanc::Toolbox::TokenizeString(tokens, contentTypeHeader, ';'); - - if (tokens.empty()) - { - return false; - } - - contentType = Orthanc::Toolbox::StripSpaces(tokens[0]); - Orthanc::Toolbox::ToLowerCase(contentType); - - if (contentType.empty()) - { - return false; - } - - bool valid = false; - subType.clear(); - - for (size_t i = 0; i < tokens.size(); i++) - { - std::vector items; - Orthanc::Toolbox::TokenizeString(items, tokens[i], '='); - - if (items.size() == 2) - { - if (boost::iequals("boundary", Orthanc::Toolbox::StripSpaces(items[0]))) - { - boundary = Orthanc::Toolbox::StripSpaces(items[1]); - valid = !boundary.empty(); - } - else if (boost::iequals("type", Orthanc::Toolbox::StripSpaces(items[0]))) - { - subType = Orthanc::Toolbox::StripSpaces(items[1]); - Orthanc::Toolbox::ToLowerCase(subType); - - // https://bitbucket.org/sjodogne/orthanc/issues/54/decide-what-to-do-wrt-quoting-of-multipart - // https://tools.ietf.org/html/rfc7231#section-3.1.1.1 - if (subType.size() >= 2 && - subType[0] == '"' && - subType[subType.size() - 1] == '"') - { - subType = subType.substr(1, subType.size() - 2); - } - } - } - } - - return valid; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/MultipartStreamReader.h --- a/Core/HttpServer/MultipartStreamReader.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "StringMatcher.h" -#include "../ChunkedBuffer.h" - -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC MultipartStreamReader : public boost::noncopyable - { - public: - typedef std::map HttpHeaders; - - class IHandler : public boost::noncopyable - { - public: - virtual ~IHandler() - { - } - - virtual void HandlePart(const HttpHeaders& headers, - const void* part, - size_t size) = 0; - }; - - private: - enum State - { - State_UnusedArea, - State_Content, - State_Done - }; - - State state_; - IHandler* handler_; - StringMatcher headersMatcher_; - StringMatcher boundaryMatcher_; - ChunkedBuffer buffer_; - size_t blockSize_; - - void ParseStream(); - - public: - MultipartStreamReader(const std::string& boundary); - - void SetBlockSize(size_t size); - - size_t GetBlockSize() const - { - return blockSize_; - } - - void SetHandler(IHandler& handler) - { - handler_ = &handler; - } - - void AddChunk(const void* chunk, - size_t size); - - void AddChunk(const std::string& chunk); - - void CloseStream(); - - static bool GetMainContentType(std::string& contentType, - const HttpHeaders& headers); - - static bool ParseMultipartContentType(std::string& contentType, - std::string& subType, // Possibly empty - std::string& boundary, - const std::string& contentTypeHeader); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/StringHttpOutput.cpp --- a/Core/HttpServer/StringHttpOutput.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "StringHttpOutput.h" - -#include "../OrthancException.h" - -namespace Orthanc -{ - void StringHttpOutput::OnHttpStatusReceived(HttpStatus status) - { - switch (status) - { - case HttpStatus_200_Ok: - found_ = true; - break; - - case HttpStatus_404_NotFound: - found_ = false; - break; - - default: - throw OrthancException(ErrorCode_BadRequest); - } - } - - void StringHttpOutput::Send(bool isHeader, const void* buffer, size_t length) - { - if (!isHeader) - { - buffer_.AddChunk(buffer, length); - } - } - - void StringHttpOutput::GetOutput(std::string& output) - { - if (found_) - { - buffer_.Flatten(output); - } - else - { - throw OrthancException(ErrorCode_UnknownResource); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/StringHttpOutput.h --- a/Core/HttpServer/StringHttpOutput.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IHttpOutputStream.h" - -#include "../ChunkedBuffer.h" - -namespace Orthanc -{ - class StringHttpOutput : public IHttpOutputStream - { - private: - bool found_; - ChunkedBuffer buffer_; - - public: - StringHttpOutput() : found_(false) - { - } - - virtual void OnHttpStatusReceived(HttpStatus status); - - virtual void Send(bool isHeader, const void* buffer, size_t length); - - virtual void DisableKeepAlive() - { - } - - void GetOutput(std::string& output); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/StringMatcher.cpp --- a/Core/HttpServer/StringMatcher.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "StringMatcher.h" - -#include "../OrthancException.h" - -#include -//#include -//#include - -namespace Orthanc -{ - class StringMatcher::Search - { - private: - typedef boost::algorithm::boyer_moore Algorithm; - //typedef boost::algorithm::boyer_moore_horspool Algorithm; - - Algorithm algorithm_; - - public: - // WARNING - The lifetime of "pattern_" must be larger than - // "search_", as the latter internally keeps a pointer to "pattern" (*) - Search(const std::string& pattern) : - algorithm_(pattern.begin(), pattern.end()) - { - } - - Iterator Apply(Iterator start, - Iterator end) const - { -#if BOOST_VERSION >= 106200 - return algorithm_(start, end).first; -#else - return algorithm_(start, end); -#endif - } - }; - - - StringMatcher::StringMatcher(const std::string& pattern) : - pattern_(pattern), - valid_(false) - { - // WARNING - Don't use "pattern" (local variable, will be - // destroyed once exiting the constructor) but "pattern_" - // (variable member, will last as long as the algorithm), - // otherwise lifetime is bad! (*) - search_.reset(new Search(pattern_)); - } - - - bool StringMatcher::Apply(Iterator start, - Iterator end) - { - assert(search_.get() != NULL); - matchBegin_ = search_->Apply(start, end); - - if (matchBegin_ == end) - { - valid_ = false; - } - else - { - matchEnd_ = matchBegin_ + pattern_.size(); - assert(matchEnd_ <= end); - valid_ = true; - } - - return valid_; - } - - - StringMatcher::Iterator StringMatcher::GetMatchBegin() const - { - if (valid_) - { - return matchBegin_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - StringMatcher::Iterator StringMatcher::GetMatchEnd() const - { - if (valid_) - { - return matchEnd_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - const char* StringMatcher::GetPointerBegin() const - { - return &GetMatchBegin()[0]; - } - - - const char* StringMatcher::GetPointerEnd() const - { - return GetPointerBegin() + pattern_.size(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/HttpServer/StringMatcher.h --- a/Core/HttpServer/StringMatcher.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../OrthancFramework.h" - -#include -#include -#include - -namespace Orthanc -{ - // Convenience class that wraps a Boost algorithm for string matching - class ORTHANC_PUBLIC StringMatcher : public boost::noncopyable - { - public: - typedef std::string::const_iterator Iterator; - - private: - class Search; - - boost::shared_ptr search_; // PImpl pattern - std::string pattern_; - bool valid_; - Iterator matchBegin_; - Iterator matchEnd_; - - public: - StringMatcher(const std::string& pattern); - - const std::string& GetPattern() const - { - return pattern_; - } - - bool IsValid() const - { - return valid_; - } - - bool Apply(Iterator start, - Iterator end); - - bool Apply(const std::string& corpus) - { - return Apply(corpus.begin(), corpus.end()); - } - - Iterator GetMatchBegin() const; - - Iterator GetMatchEnd() const; - - const char* GetPointerBegin() const; - - const char* GetPointerEnd() const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/IDynamicObject.h --- a/Core/IDynamicObject.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "OrthancFramework.h" - -#include - -namespace Orthanc -{ - /** - * This class should be the ancestor to any class whose type is - * determined at the runtime, and that can be dynamically allocated. - * Being a child of IDynamicObject only implies the existence of a - * virtual destructor. - **/ - class ORTHANC_PUBLIC IDynamicObject : public boost::noncopyable - { - public: - virtual ~IDynamicObject() - { - } - }; - - - /** - * This class is a simple implementation of a IDynamicObject that - * stores a single typed value. - */ - template - class SingleValueObject : public IDynamicObject - { - private: - T value_; - - public: - explicit SingleValueObject(const T& value) : - value_(value) - { - } - - const T& GetValue() const - { - return value_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/Font.cpp --- a/Core/Images/Font.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,419 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "Font.h" - -#if !defined(ORTHANC_ENABLE_LOCALE) -# error ORTHANC_ENABLE_LOCALE must be defined to use this file -#endif - -#if ORTHANC_SANDBOXED == 0 -# include "../SystemToolbox.h" -#endif - -#include "../OrthancException.h" -#include "../Toolbox.h" -#include "Image.h" -#include "ImageProcessing.h" - -#include -#include -#include - -namespace Orthanc -{ - Font::~Font() - { - for (Characters::iterator it = characters_.begin(); - it != characters_.end(); ++it) - { - delete it->second; - } - } - - - void Font::LoadFromMemory(const std::string& font) - { - Json::Value v; - Json::Reader reader; - if (!reader.parse(font, v) || - v.type() != Json::objectValue || - !v.isMember("Name") || - !v.isMember("Size") || - !v.isMember("Characters") || - v["Name"].type() != Json::stringValue || - v["Size"].type() != Json::intValue || - v["Characters"].type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFont); - } - - name_ = v["Name"].asString(); - size_ = v["Size"].asUInt(); - maxHeight_ = 0; - - Json::Value::Members characters = v["Characters"].getMemberNames(); - - for (size_t i = 0; i < characters.size(); i++) - { - const Json::Value& info = v["Characters"][characters[i]]; - if (info.type() != Json::objectValue || - !info.isMember("Advance") || - !info.isMember("Bitmap") || - !info.isMember("Height") || - !info.isMember("Top") || - !info.isMember("Width") || - info["Advance"].type() != Json::intValue || - info["Bitmap"].type() != Json::arrayValue || - info["Height"].type() != Json::intValue || - info["Top"].type() != Json::intValue || - info["Width"].type() != Json::intValue) - { - throw OrthancException(ErrorCode_BadFont); - } - - std::unique_ptr c(new Character); - - c->advance_ = info["Advance"].asUInt(); - c->height_ = info["Height"].asUInt(); - c->top_ = info["Top"].asUInt(); - c->width_ = info["Width"].asUInt(); - c->bitmap_.resize(info["Bitmap"].size()); - - if (c->height_ > maxHeight_) - { - maxHeight_ = c->height_; - } - - for (Json::Value::ArrayIndex j = 0; j < info["Bitmap"].size(); j++) - { - if (info["Bitmap"][j].type() != Json::intValue) - { - throw OrthancException(ErrorCode_BadFont); - } - - int value = info["Bitmap"][j].asInt(); - if (value < 0 || value > 255) - { - throw OrthancException(ErrorCode_BadFont); - } - - c->bitmap_[j] = static_cast(value); - } - - int index = boost::lexical_cast(characters[i]); - if (index < 0 || index > 255) - { - throw OrthancException(ErrorCode_BadFont); - } - - characters_[static_cast(index)] = c.release(); - } - } - - -#if ORTHANC_SANDBOXED == 0 - void Font::LoadFromFile(const std::string& path) - { - std::string font; - SystemToolbox::ReadFile(font, path); - LoadFromMemory(font); - } -#endif - - - static unsigned int MyMin(unsigned int a, - unsigned int b) - { - return a < b ? a : b; - } - - - void Font::DrawCharacter(ImageAccessor& target, - const Character& character, - int x, - int y, - const uint8_t color[4]) const - { - // Compute the bounds of the character - if (x >= static_cast(target.GetWidth()) || - y >= static_cast(target.GetHeight())) - { - // The character is out of the image - return; - } - - unsigned int left = x < 0 ? -x : 0; - unsigned int top = y < 0 ? -y : 0; - unsigned int width = MyMin(character.width_, target.GetWidth() - x); - unsigned int height = MyMin(character.height_, target.GetHeight() - y); - - unsigned int bpp = target.GetBytesPerPixel(); - - // Blit the font bitmap OVER the target image - // https://en.wikipedia.org/wiki/Alpha_compositing - - for (unsigned int cy = top; cy < height; cy++) - { - uint8_t* p = reinterpret_cast(target.GetRow(y + cy)) + (x + left) * bpp; - unsigned int pos = cy * character.width_ + left; - - switch (target.GetFormat()) - { - case PixelFormat_Grayscale8: - { - assert(bpp == 1); - for (unsigned int cx = left; cx < width; cx++, pos++, p++) - { - uint16_t alpha = character.bitmap_[pos]; - uint16_t value = alpha * static_cast(color[0]) + (255 - alpha) * static_cast(*p); - *p = static_cast(value >> 8); - } - - break; - } - - case PixelFormat_RGB24: - { - assert(bpp == 3); - for (unsigned int cx = left; cx < width; cx++, pos++, p += 3) - { - uint16_t alpha = character.bitmap_[pos]; - for (uint8_t i = 0; i < 3; i++) - { - uint16_t value = alpha * static_cast(color[i]) + (255 - alpha) * static_cast(p[i]); - p[i] = static_cast(value >> 8); - } - } - - break; - } - - case PixelFormat_RGBA32: - case PixelFormat_BGRA32: - { - assert(bpp == 4); - - for (unsigned int cx = left; cx < width; cx++, pos++, p += 4) - { - float alpha = static_cast(character.bitmap_[pos]) / 255.0f; - float beta = (1.0f - alpha) * static_cast(p[3]) / 255.0f; - float denom = 1.0f / (alpha + beta); - - for (uint8_t i = 0; i < 3; i++) - { - p[i] = static_cast((alpha * static_cast(color[i]) + - beta * static_cast(p[i])) * denom); - } - - p[3] = static_cast(255.0f * (alpha + beta)); - } - - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - } - - - void Font::DrawInternal(ImageAccessor& target, - const std::string& utf8, - int x, - int y, - const uint8_t color[4]) const - { - if (target.GetFormat() != PixelFormat_Grayscale8 && - target.GetFormat() != PixelFormat_RGB24 && - target.GetFormat() != PixelFormat_RGBA32 && - target.GetFormat() != PixelFormat_BGRA32) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - int a = x; - -#if ORTHANC_ENABLE_LOCALE == 1 - std::string s = Toolbox::ConvertFromUtf8(utf8, Encoding_Latin1); -#else - // If the locale support is disabled, simply drop non-ASCII - // characters from the source UTF-8 string - std::string s = Toolbox::ConvertToAscii(utf8); -#endif - - for (size_t i = 0; i < s.size(); i++) - { - if (s[i] == '\n') - { - // Go to the next line - a = x; - y += maxHeight_ + 1; - } - else - { - Characters::const_iterator c = characters_.find(s[i]); - if (c != characters_.end()) - { - DrawCharacter(target, *c->second, a, y + static_cast(c->second->top_), color); - a += c->second->advance_; - } - } - } - } - - - void Font::Draw(ImageAccessor& target, - const std::string& utf8, - int x, - int y, - uint8_t grayscale) const - { - uint8_t color[4] = { grayscale, grayscale, grayscale, 255 }; - DrawInternal(target, utf8, x, y, color); - } - - - void Font::Draw(ImageAccessor& target, - const std::string& utf8, - int x, - int y, - uint8_t r, - uint8_t g, - uint8_t b) const - { - uint8_t color[4]; - - switch (target.GetFormat()) - { - case PixelFormat_BGRA32: - color[0] = b; - color[1] = g; - color[2] = r; - color[3] = 255; - break; - - default: - color[0] = r; - color[1] = g; - color[2] = b; - color[3] = 255; - break; - } - - DrawInternal(target, utf8, x, y, color); - } - - - void Font::ComputeTextExtent(unsigned int& width, - unsigned int& height, - const std::string& utf8) const - { - width = 0; - height = 0; - -#if ORTHANC_ENABLE_LOCALE == 1 - std::string s = Toolbox::ConvertFromUtf8(utf8, Encoding_Latin1); -#else - // If the locale support is disabled, simply drop non-ASCII - // characters from the source UTF-8 string - std::string s = Toolbox::ConvertToAscii(utf8); -#endif - - // Compute the text extent - unsigned int x = 0; - unsigned int y = 0; - - for (size_t i = 0; i < s.size(); i++) - { - if (s[i] == '\n') - { - // Go to the next line - x = 0; - y += (maxHeight_ + 1); - } - else - { - Characters::const_iterator c = characters_.find(s[i]); - if (c != characters_.end()) - { - x += c->second->advance_; - - unsigned int bottom = y + c->second->top_ + c->second->height_; - if (bottom > height) - { - height = bottom; - } - - if (x > width) - { - width = x; - } - } - } - } - } - - - ImageAccessor* Font::Render(const std::string& utf8, - PixelFormat format, - uint8_t r, - uint8_t g, - uint8_t b) const - { - unsigned int width, height; - ComputeTextExtent(width, height, utf8); - - std::unique_ptr target(new Image(format, width, height, false)); - ImageProcessing::Set(*target, 0, 0, 0, 255); - Draw(*target, utf8, 0, 0, r, g, b); - - return target.release(); - } - - - ImageAccessor* Font::RenderAlpha(const std::string& utf8) const - { - unsigned int width, height; - ComputeTextExtent(width, height, utf8); - - std::unique_ptr target(new Image(PixelFormat_Grayscale8, width, height, false)); - ImageProcessing::Set(*target, 0); - Draw(*target, utf8, 0, 0, 255); - - return target.release(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/Font.h --- a/Core/Images/Font.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../OrthancFramework.h" - -#include "ImageAccessor.h" - -#include -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC Font : public boost::noncopyable - { - private: - struct Character - { - unsigned int width_; - unsigned int height_; - unsigned int top_; - unsigned int advance_; - std::vector bitmap_; - }; - - typedef std::map Characters; - - std::string name_; - unsigned int size_; - Characters characters_; - unsigned int maxHeight_; - - void DrawCharacter(ImageAccessor& target, - const Character& character, - int x, - int y, - const uint8_t color[4]) const; - - void DrawInternal(ImageAccessor& target, - const std::string& utf8, - int x, - int y, - const uint8_t color[4]) const; - - public: - Font() : - size_(0), - maxHeight_(0) - { - } - - ~Font(); - - void LoadFromMemory(const std::string& font); - -#if ORTHANC_SANDBOXED == 0 - void LoadFromFile(const std::string& path); -#endif - - const std::string& GetName() const - { - return name_; - } - - unsigned int GetSize() const - { - return size_; - } - - void Draw(ImageAccessor& target, - const std::string& utf8, - int x, - int y, - uint8_t grayscale) const; - - void Draw(ImageAccessor& target, - const std::string& utf8, - int x, - int y, - uint8_t r, - uint8_t g, - uint8_t b) const; - - void ComputeTextExtent(unsigned int& width, - unsigned int& height, - const std::string& utf8) const; - - ImageAccessor* Render(const std::string& utf8, - PixelFormat format, - uint8_t r, - uint8_t g, - uint8_t b) const; - - ImageAccessor* RenderAlpha(const std::string& utf8) const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/FontRegistry.cpp --- a/Core/Images/FontRegistry.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "FontRegistry.h" - -#include "../OrthancException.h" - -#include - -namespace Orthanc -{ - FontRegistry::~FontRegistry() - { - for (Fonts::iterator it = fonts_.begin(); it != fonts_.end(); ++it) - { - delete *it; - } - } - - - void FontRegistry::AddFromMemory(const std::string& font) - { - std::unique_ptr f(new Font); - f->LoadFromMemory(font); - fonts_.push_back(f.release()); - } - - -#if ORTHANC_SANDBOXED == 0 - void FontRegistry::AddFromFile(const std::string& path) - { - std::unique_ptr f(new Font); - f->LoadFromFile(path); - fonts_.push_back(f.release()); - } -#endif - - - const Font& FontRegistry::GetFont(size_t i) const - { - if (i >= fonts_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - return *fonts_[i]; - } - } - - const Font* FontRegistry::FindFont(const std::string& fontName) const - { - for (Fonts::const_iterator it = fonts_.begin(); it != fonts_.end(); it++) - { - if ((*it)->GetName() == fontName) - { - return *it; - } - } - - return NULL; - } - -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/FontRegistry.h --- a/Core/Images/FontRegistry.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "Font.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC FontRegistry : public boost::noncopyable - { - private: - typedef std::vector Fonts; - - Fonts fonts_; - - public: - ~FontRegistry(); - - void AddFromMemory(const std::string& font); - -#if ORTHANC_SANDBOXED == 0 - void AddFromFile(const std::string& path); -#endif - - size_t GetSize() const - { - return fonts_.size(); - } - - const Font& GetFont(size_t i) const; - - const Font* FindFont(const std::string& fontName) const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/IImageWriter.cpp --- a/Core/Images/IImageWriter.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "IImageWriter.h" - -#if ORTHANC_SANDBOXED == 0 -# include "../SystemToolbox.h" -#endif - -namespace Orthanc -{ -#if ORTHANC_SANDBOXED == 0 - void IImageWriter::WriteToFileInternal(const std::string& path, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - std::string compressed; - WriteToMemoryInternal(compressed, width, height, pitch, format, buffer); - SystemToolbox::WriteFile(compressed, path); - } -#endif -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/IImageWriter.h --- a/Core/Images/IImageWriter.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ImageAccessor.h" - -#include - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -namespace Orthanc -{ - class IImageWriter : public boost::noncopyable - { - protected: - virtual void WriteToMemoryInternal(std::string& compressed, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) = 0; - -#if ORTHANC_SANDBOXED == 0 - virtual void WriteToFileInternal(const std::string& path, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); -#endif - - public: - virtual ~IImageWriter() - { - } - - virtual void WriteToMemory(std::string& compressed, - const ImageAccessor& accessor) - { - WriteToMemoryInternal(compressed, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); - } - -#if ORTHANC_SANDBOXED == 0 - virtual void WriteToFile(const std::string& path, - const ImageAccessor& accessor) - { - WriteToFileInternal(path, accessor.GetWidth(), accessor.GetHeight(), - accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer()); - } -#endif - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/Image.cpp --- a/Core/Images/Image.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "Image.h" - -#include "../Compatibility.h" -#include "ImageProcessing.h" - -#include - -namespace Orthanc -{ - Image::Image(PixelFormat format, - unsigned int width, - unsigned int height, - bool forceMinimalPitch) : - image_(format, width, height, forceMinimalPitch) - { - ImageAccessor accessor; - image_.GetWriteableAccessor(accessor); - - AssignWritable(format, width, height, accessor.GetPitch(), accessor.GetBuffer()); - } - - - Image* Image::Clone(const ImageAccessor& source) - { - std::unique_ptr target(new Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false)); - ImageProcessing::Copy(*target, source); - return target.release(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/Image.h --- a/Core/Images/Image.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ImageAccessor.h" -#include "ImageBuffer.h" - -namespace Orthanc -{ - class ORTHANC_PUBLIC Image : public ImageAccessor - { - private: - ImageBuffer image_; - - public: - Image(PixelFormat format, - unsigned int width, - unsigned int height, - bool forceMinimalPitch); - - static Image* Clone(const ImageAccessor& source); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/ImageAccessor.cpp --- a/Core/Images/ImageAccessor.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,307 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "ImageAccessor.h" - -#include "../Logging.h" -#include "../OrthancException.h" -#include "../ChunkedBuffer.h" - -#include -#include -#include - - - -namespace Orthanc -{ - template - static void ToMatlabStringInternal(ChunkedBuffer& target, - const ImageAccessor& source) - { - target.AddChunk("double([ "); - - for (unsigned int y = 0; y < source.GetHeight(); y++) - { - const PixelType* p = reinterpret_cast(source.GetConstRow(y)); - - std::string s; - if (y > 0) - { - s = "; "; - } - - s.reserve(source.GetWidth() * 8); - - for (unsigned int x = 0; x < source.GetWidth(); x++, p++) - { - s += boost::lexical_cast(static_cast(*p)) + " "; - } - - target.AddChunk(s); - } - - target.AddChunk("])"); - } - - - static void RGB24ToMatlabString(ChunkedBuffer& target, - const ImageAccessor& source) - { - assert(source.GetFormat() == PixelFormat_RGB24); - - target.AddChunk("double(permute(reshape([ "); - - for (unsigned int y = 0; y < source.GetHeight(); y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - - std::string s; - s.reserve(source.GetWidth() * 3 * 8); - - for (unsigned int x = 0; x < 3 * source.GetWidth(); x++, p++) - { - s += boost::lexical_cast(static_cast(*p)) + " "; - } - - target.AddChunk(s); - } - - target.AddChunk("], [ 3 " + boost::lexical_cast(source.GetHeight()) + - " " + boost::lexical_cast(source.GetWidth()) + " ]), [ 3 2 1 ]))"); - } - - - void* ImageAccessor::GetBuffer() const - { - if (readOnly_) - { - throw OrthancException(ErrorCode_ReadOnly, - "Trying to write to a read-only image"); - } - - return buffer_; - } - - - const void* ImageAccessor::GetConstRow(unsigned int y) const - { - if (buffer_ != NULL) - { - return buffer_ + y * pitch_; - } - else - { - return NULL; - } - } - - - void* ImageAccessor::GetRow(unsigned int y) const - { - if (readOnly_) - { - throw OrthancException(ErrorCode_ReadOnly, - "Trying to write to a read-only image"); - } - - if (buffer_ != NULL) - { - return buffer_ + y * pitch_; - } - else - { - return NULL; - } - } - - - void ImageAccessor::AssignEmpty(PixelFormat format) - { - readOnly_ = false; - format_ = format; - width_ = 0; - height_ = 0; - pitch_ = 0; - buffer_ = NULL; - } - - - void ImageAccessor::AssignReadOnly(PixelFormat format, - unsigned int width, - unsigned int height, - unsigned int pitch, - const void *buffer) - { - readOnly_ = true; - format_ = format; - width_ = width; - height_ = height; - pitch_ = pitch; - buffer_ = reinterpret_cast(const_cast(buffer)); - - if (GetBytesPerPixel() * width_ > pitch_) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - void ImageAccessor::AssignWritable(PixelFormat format, - unsigned int width, - unsigned int height, - unsigned int pitch, - void *buffer) - { - readOnly_ = false; - format_ = format; - width_ = width; - height_ = height; - pitch_ = pitch; - buffer_ = reinterpret_cast(buffer); - - if (GetBytesPerPixel() * width_ > pitch_) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - void ImageAccessor::GetWriteableAccessor(ImageAccessor& target) const - { - if (readOnly_) - { - throw OrthancException(ErrorCode_ReadOnly); - } - else - { - target.AssignWritable(format_, width_, height_, pitch_, buffer_); - } - } - - - void ImageAccessor::ToMatlabString(std::string& target) const - { - ChunkedBuffer buffer; - - switch (GetFormat()) - { - case PixelFormat_Grayscale8: - ToMatlabStringInternal(buffer, *this); - break; - - case PixelFormat_Grayscale16: - ToMatlabStringInternal(buffer, *this); - break; - - case PixelFormat_Grayscale32: - ToMatlabStringInternal(buffer, *this); - break; - - case PixelFormat_Grayscale64: - ToMatlabStringInternal(buffer, *this); - break; - - case PixelFormat_SignedGrayscale16: - ToMatlabStringInternal(buffer, *this); - break; - - case PixelFormat_Float32: - ToMatlabStringInternal(buffer, *this); - break; - - case PixelFormat_RGB24: - RGB24ToMatlabString(buffer, *this); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - buffer.Flatten(target); - } - - - - void ImageAccessor::GetRegion(ImageAccessor& accessor, - unsigned int x, - unsigned int y, - unsigned int width, - unsigned int height) const - { - if (x + width > width_ || - y + height > height_) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (width == 0 || - height == 0) - { - accessor.AssignWritable(format_, 0, 0, 0, NULL); - } - else - { - uint8_t* p = (buffer_ + - y * pitch_ + - x * GetBytesPerPixel()); - - if (readOnly_) - { - accessor.AssignReadOnly(format_, width, height, pitch_, p); - } - else - { - accessor.AssignWritable(format_, width, height, pitch_, p); - } - } - } - - - void ImageAccessor::SetFormat(PixelFormat format) - { - if (readOnly_) - { - throw OrthancException(ErrorCode_ReadOnly, - "Trying to modify the format of a read-only image"); - } - - if (::Orthanc::GetBytesPerPixel(format) != ::Orthanc::GetBytesPerPixel(format_)) - { - throw OrthancException(ErrorCode_IncompatibleImageFormat); - } - - format_ = format; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/ImageAccessor.h --- a/Core/Images/ImageAccessor.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC ImageAccessor : public boost::noncopyable - { - private: - template - friend struct ImageTraits; - - bool readOnly_; - PixelFormat format_; - unsigned int width_; - unsigned int height_; - unsigned int pitch_; - uint8_t *buffer_; - - template - const T& GetPixelUnchecked(unsigned int x, - unsigned int y) const - { - const uint8_t* row = reinterpret_cast(buffer_) + y * pitch_; - return reinterpret_cast(row) [x]; - } - - - template - T& GetPixelUnchecked(unsigned int x, - unsigned int y) - { - uint8_t* row = reinterpret_cast(buffer_) + y * pitch_; - return reinterpret_cast(row) [x]; - } - - public: - ImageAccessor() - { - AssignEmpty(PixelFormat_Grayscale8); - } - - virtual ~ImageAccessor() - { - } - - bool IsReadOnly() const - { - return readOnly_; - } - - PixelFormat GetFormat() const - { - return format_; - } - - unsigned int GetBytesPerPixel() const - { - return ::Orthanc::GetBytesPerPixel(format_); - } - - unsigned int GetWidth() const - { - return width_; - } - - unsigned int GetHeight() const - { - return height_; - } - - unsigned int GetPitch() const - { - return pitch_; - } - - unsigned int GetSize() const - { - return GetHeight() * GetPitch(); - } - - const void* GetConstBuffer() const - { - return buffer_; - } - - void* GetBuffer() const; - - const void* GetConstRow(unsigned int y) const; - - void* GetRow(unsigned int y) const; - - void AssignEmpty(PixelFormat format); - - void AssignReadOnly(PixelFormat format, - unsigned int width, - unsigned int height, - unsigned int pitch, - const void *buffer); - - void GetReadOnlyAccessor(ImageAccessor& target) const - { - target.AssignReadOnly(format_, width_, height_, pitch_, buffer_); - } - - void AssignWritable(PixelFormat format, - unsigned int width, - unsigned int height, - unsigned int pitch, - void *buffer); - - void GetWriteableAccessor(ImageAccessor& target) const; - - void ToMatlabString(std::string& target) const; - - void GetRegion(ImageAccessor& accessor, - unsigned int x, - unsigned int y, - unsigned int width, - unsigned int height) const; - - void SetFormat(PixelFormat format); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/ImageBuffer.cpp --- a/Core/Images/ImageBuffer.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,180 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "ImageBuffer.h" - -#include "../OrthancException.h" - -#include -#include - -namespace Orthanc -{ - void ImageBuffer::Allocate() - { - if (changed_) - { - Deallocate(); - - /* - if (forceMinimalPitch_) - { - TODO: Align pitch and memory buffer to optimal size for SIMD. - } - */ - - pitch_ = GetBytesPerPixel() * width_; - size_t size = pitch_ * height_; - - if (size == 0) - { - buffer_ = NULL; - } - else - { - buffer_ = malloc(size); - if (buffer_ == NULL) - { - throw OrthancException(ErrorCode_NotEnoughMemory, - "Failed to allocate an image buffer of size " + boost::lexical_cast(width_) + "x" + boost::lexical_cast(height_)); - } - } - - changed_ = false; - } - } - - - void ImageBuffer::Deallocate() - { - if (buffer_ != NULL) - { - free(buffer_); - buffer_ = NULL; - changed_ = true; - } - } - - - ImageBuffer::ImageBuffer(PixelFormat format, - unsigned int width, - unsigned int height, - bool forceMinimalPitch) : - forceMinimalPitch_(forceMinimalPitch) - { - Initialize(); - SetWidth(width); - SetHeight(height); - SetFormat(format); - } - - - void ImageBuffer::Initialize() - { - changed_ = false; - forceMinimalPitch_ = true; - format_ = PixelFormat_Grayscale8; - width_ = 0; - height_ = 0; - pitch_ = 0; - buffer_ = NULL; - } - - - void ImageBuffer::SetFormat(PixelFormat format) - { - if (format != format_) - { - changed_ = true; - format_ = format; - } - } - - - void ImageBuffer::SetWidth(unsigned int width) - { - if (width != width_) - { - changed_ = true; - width_ = width; - } - } - - - void ImageBuffer::SetHeight(unsigned int height) - { - if (height != height_) - { - changed_ = true; - height_ = height; - } - } - - - void ImageBuffer::GetReadOnlyAccessor(ImageAccessor& accessor) - { - Allocate(); - accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_); - } - - - void ImageBuffer::GetWriteableAccessor(ImageAccessor& accessor) - { - Allocate(); - accessor.AssignWritable(format_, width_, height_, pitch_, buffer_); - } - - - void ImageBuffer::AcquireOwnership(ImageBuffer& other) - { - // Remove the content of the current image - Deallocate(); - - // Force the allocation of the other image (if not already - // allocated) - other.Allocate(); - - // Transfer the content of the other image - changed_ = false; - forceMinimalPitch_ = other.forceMinimalPitch_; - format_ = other.format_; - width_ = other.width_; - height_ = other.height_; - pitch_ = other.pitch_; - buffer_ = other.buffer_; - - // Force the reinitialization of the other image - other.Initialize(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/ImageBuffer.h --- a/Core/Images/ImageBuffer.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ImageAccessor.h" - -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC ImageBuffer : public boost::noncopyable - { - private: - bool changed_; - - bool forceMinimalPitch_; // Currently unused - PixelFormat format_; - unsigned int width_; - unsigned int height_; - unsigned int pitch_; - void *buffer_; - - void Initialize(); - - void Allocate(); - - void Deallocate(); - - public: - ImageBuffer(PixelFormat format, - unsigned int width, - unsigned int height, - bool forceMinimalPitch); - - ImageBuffer() - { - Initialize(); - } - - ~ImageBuffer() - { - Deallocate(); - } - - PixelFormat GetFormat() const - { - return format_; - } - - void SetFormat(PixelFormat format); - - unsigned int GetWidth() const - { - return width_; - } - - void SetWidth(unsigned int width); - - unsigned int GetHeight() const - { - return height_; - } - - void SetHeight(unsigned int height); - - unsigned int GetBytesPerPixel() const - { - return ::Orthanc::GetBytesPerPixel(format_); - } - - void GetReadOnlyAccessor(ImageAccessor& accessor); - - void GetWriteableAccessor(ImageAccessor& accessor); - - bool IsMinimalPitchForced() const - { - return forceMinimalPitch_; - } - - void AcquireOwnership(ImageBuffer& other); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/ImageProcessing.cpp --- a/Core/Images/ImageProcessing.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2466 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "ImageProcessing.h" - -#include "Image.h" -#include "ImageTraits.h" -#include "PixelTraits.h" -#include "../OrthancException.h" - -#ifdef __EMSCRIPTEN__ -/* - Avoid this error: - ----------------- - .../boost/math/special_functions/round.hpp:118:12: warning: implicit conversion from 'std::__2::numeric_limits::type' (aka 'long long') to 'float' changes value from 9223372036854775807 to 9223372036854775808 [-Wimplicit-int-float-conversion] - .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:333:28: note: in instantiation of function template specialization 'boost::math::llround' requested here - .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:1006:9: note: in instantiation of function template specialization 'Orthanc::MultiplyConstantInternal' requested here -*/ -#pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" -#endif - -#include - -#include -#include -#include -#include -#include - -namespace Orthanc -{ - double ImageProcessing::ImagePoint::GetDistanceTo(const ImagePoint& other) const - { - double dx = (double)(other.GetX() - GetX()); - double dy = (double)(other.GetY() - GetY()); - return sqrt(dx * dx + dy * dy); - } - - double ImageProcessing::ImagePoint::GetDistanceToLine(double a, double b, double c) const // where ax + by + c = 0 is the equation of the line - { - return std::abs(a * static_cast(GetX()) + b * static_cast(GetY()) + c) / pow(a * a + b * b, 0.5); - } - - template - static void ConvertInternal(ImageAccessor& target, - const ImageAccessor& source) - { - // WARNING - "::min()" should be replaced by "::lowest()" if - // dealing with float or double (which is not the case so far) - assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double" - const TargetType minValue = std::numeric_limits::min(); - const TargetType maxValue = std::numeric_limits::max(); - - const unsigned int width = source.GetWidth(); - const unsigned int height = source.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - TargetType* t = reinterpret_cast(target.GetRow(y)); - const SourceType* s = reinterpret_cast(source.GetConstRow(y)); - - for (unsigned int x = 0; x < width; x++, t++, s++) - { - if (static_cast(*s) < static_cast(minValue)) - { - *t = minValue; - } - else if (static_cast(*s) > static_cast(maxValue)) - { - *t = maxValue; - } - else - { - *t = static_cast(*s); - } - } - } - } - - - template - static void ConvertGrayscaleToFloat(ImageAccessor& target, - const ImageAccessor& source) - { - assert(sizeof(float) == 4); - - const unsigned int width = source.GetWidth(); - const unsigned int height = source.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - float* t = reinterpret_cast(target.GetRow(y)); - const SourceType* s = reinterpret_cast(source.GetConstRow(y)); - - for (unsigned int x = 0; x < width; x++, t++, s++) - { - *t = static_cast(*s); - } - } - } - - - template - static void ConvertFloatToGrayscale(ImageAccessor& target, - const ImageAccessor& source) - { - typedef typename PixelTraits::PixelType TargetType; - - assert(sizeof(float) == 4); - - const unsigned int width = source.GetWidth(); - const unsigned int height = source.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - TargetType* q = reinterpret_cast(target.GetRow(y)); - const float* p = reinterpret_cast(source.GetConstRow(y)); - - for (unsigned int x = 0; x < width; x++, p++, q++) - { - PixelTraits::FloatToPixel(*q, *p); - } - } - } - - - template - static void ConvertColorToGrayscale(ImageAccessor& target, - const ImageAccessor& source) - { - assert(source.GetFormat() == PixelFormat_RGB24); - - // WARNING - "::min()" should be replaced by "::lowest()" if - // dealing with float or double (which is not the case so far) - assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double" - const TargetType minValue = std::numeric_limits::min(); - const TargetType maxValue = std::numeric_limits::max(); - - const unsigned int width = source.GetWidth(); - const unsigned int height = source.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - TargetType* t = reinterpret_cast(target.GetRow(y)); - const uint8_t* s = reinterpret_cast(source.GetConstRow(y)); - - for (unsigned int x = 0; x < width; x++, t++, s += 3) - { - // Y = 0.2126 R + 0.7152 G + 0.0722 B - int32_t v = (2126 * static_cast(s[0]) + - 7152 * static_cast(s[1]) + - 0722 * static_cast(s[2])) / 10000; - - if (static_cast(v) < static_cast(minValue)) - { - *t = minValue; - } - else if (static_cast(v) > static_cast(maxValue)) - { - *t = maxValue; - } - else - { - *t = static_cast(v); - } - } - } - } - - - static void MemsetZeroInternal(ImageAccessor& image) - { - const unsigned int height = image.GetHeight(); - const size_t lineSize = image.GetBytesPerPixel() * image.GetWidth(); - const size_t pitch = image.GetPitch(); - - uint8_t *p = reinterpret_cast(image.GetBuffer()); - - for (unsigned int y = 0; y < height; y++) - { - memset(p, 0, lineSize); - p += pitch; - } - } - - - template - static void SetInternal(ImageAccessor& image, - int64_t constant) - { - if (constant == 0 && - (image.GetFormat() == PixelFormat_Grayscale8 || - image.GetFormat() == PixelFormat_Grayscale16 || - image.GetFormat() == PixelFormat_Grayscale32 || - image.GetFormat() == PixelFormat_Grayscale64 || - image.GetFormat() == PixelFormat_SignedGrayscale16)) - { - MemsetZeroInternal(image); - } - else - { - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - PixelType* p = reinterpret_cast(image.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++) - { - *p = static_cast(constant); - } - } - } - } - - - template - static void GetMinMaxValueInternal(PixelType& minValue, - PixelType& maxValue, - const ImageAccessor& source, - const PixelType LowestValue = std::numeric_limits::min()) - { - // Deal with the special case of empty image - if (source.GetWidth() == 0 || - source.GetHeight() == 0) - { - minValue = 0; - maxValue = 0; - return; - } - - minValue = std::numeric_limits::max(); - maxValue = LowestValue; - - const unsigned int height = source.GetHeight(); - const unsigned int width = source.GetWidth(); - - for (unsigned int y = 0; y < height; y++) - { - const PixelType* p = reinterpret_cast(source.GetConstRow(y)); - - for (unsigned int x = 0; x < width; x++, p++) - { - if (*p < minValue) - { - minValue = *p; - } - - if (*p > maxValue) - { - maxValue = *p; - } - } - } - } - - - - template - static void AddConstantInternal(ImageAccessor& image, - int64_t constant) - { - if (constant == 0) - { - return; - } - - // WARNING - "::min()" should be replaced by "::lowest()" if - // dealing with float or double (which is not the case so far) - assert(sizeof(PixelType) <= 2); // Safeguard to remember about "float/double" - const int64_t minValue = std::numeric_limits::min(); - const int64_t maxValue = std::numeric_limits::max(); - - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - PixelType* p = reinterpret_cast(image.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++) - { - int64_t v = static_cast(*p) + constant; - - if (v > maxValue) - { - *p = std::numeric_limits::max(); - } - else if (v < minValue) - { - *p = std::numeric_limits::min(); - } - else - { - *p = static_cast(v); - } - } - } - } - - - - template - static void MultiplyConstantInternal(ImageAccessor& image, - float factor) - { - if (std::abs(factor - 1.0f) <= std::numeric_limits::epsilon()) - { - return; - } - - // WARNING - "::min()" should be replaced by "::lowest()" if - // dealing with float or double (which is not the case so far) - assert(sizeof(PixelType) <= 2); // Safeguard to remember about "float/double" - const int64_t minValue = std::numeric_limits::min(); - const int64_t maxValue = std::numeric_limits::max(); - - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - PixelType* p = reinterpret_cast(image.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++) - { - int64_t v; - if (UseRound) - { - // The "round" operation is very costly - v = boost::math::llround(static_cast(*p) * factor); - } - else - { - v = static_cast(static_cast(*p) * factor); - } - - if (v > maxValue) - { - *p = std::numeric_limits::max(); - } - else if (v < minValue) - { - *p = std::numeric_limits::min(); - } - else - { - *p = static_cast(v); - } - } - } - } - - - // Computes "a * x + b" at each pixel => Note that this is not the - // same convention as in "ShiftScale()" - template - static void ShiftScaleInternal(ImageAccessor& target, - const ImageAccessor& source, - float a, - float b, - const TargetType LowestValue) - // This function can be applied inplace (source == target) - { - if (source.GetWidth() != target.GetWidth() || - source.GetHeight() != target.GetHeight()) - { - throw OrthancException(ErrorCode_IncompatibleImageSize); - } - - if (&source == &target && - source.GetFormat() != target.GetFormat()) - { - throw OrthancException(ErrorCode_IncompatibleImageFormat); - } - - const TargetType minPixelValue = LowestValue; - const TargetType maxPixelValue = std::numeric_limits::max(); - const float minFloatValue = static_cast(LowestValue); - const float maxFloatValue = static_cast(maxPixelValue); - - const unsigned int height = target.GetHeight(); - const unsigned int width = target.GetWidth(); - - for (unsigned int y = 0; y < height; y++) - { - TargetType* p = reinterpret_cast(target.GetRow(y)); - const SourceType* q = reinterpret_cast(source.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++, q++) - { - float v = a * static_cast(*q) + b; - - if (v >= maxFloatValue) - { - *p = maxPixelValue; - } - else if (v <= minFloatValue) - { - *p = minPixelValue; - } - else if (UseRound) - { - // The "round" operation is very costly - *p = static_cast(boost::math::iround(v)); - } - else - { - *p = static_cast(std::floor(v)); - } - - if (Invert) - { - *p = maxPixelValue - *p; - } - } - } - } - - template - static void ShiftRightInternal(ImageAccessor& image, - unsigned int shift) - { - const unsigned int height = image.GetHeight(); - const unsigned int width = image.GetWidth(); - - for (unsigned int y = 0; y < height; y++) - { - PixelType* p = reinterpret_cast(image.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++) - { - *p = *p >> shift; - } - } - } - - template - static void ShiftLeftInternal(ImageAccessor& image, - unsigned int shift) - { - const unsigned int height = image.GetHeight(); - const unsigned int width = image.GetWidth(); - - for (unsigned int y = 0; y < height; y++) - { - PixelType* p = reinterpret_cast(image.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++) - { - *p = *p << shift; - } - } - } - - void ImageProcessing::Copy(ImageAccessor& target, - const ImageAccessor& source) - { - if (target.GetWidth() != source.GetWidth() || - target.GetHeight() != source.GetHeight()) - { - throw OrthancException(ErrorCode_IncompatibleImageSize); - } - - if (target.GetFormat() != source.GetFormat()) - { - throw OrthancException(ErrorCode_IncompatibleImageFormat); - } - - unsigned int lineSize = source.GetBytesPerPixel() * source.GetWidth(); - - assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize); - - for (unsigned int y = 0; y < source.GetHeight(); y++) - { - memcpy(target.GetRow(y), source.GetConstRow(y), lineSize); - } - } - - template - static void ApplyWindowingInternal(ImageAccessor& target, - const ImageAccessor& source, - float windowCenter, - float windowWidth, - float rescaleSlope, - float rescaleIntercept, - bool invert) - { - assert(sizeof(SourceType) == source.GetBytesPerPixel() && - sizeof(TargetType) == target.GetBytesPerPixel()); - - // WARNING - "::min()" should be replaced by "::lowest()" if - // dealing with float or double (which is not the case so far) - assert(sizeof(TargetType) <= 2); // Safeguard to remember about "float/double" - const TargetType minTargetValue = std::numeric_limits::min(); - const TargetType maxTargetValue = std::numeric_limits::max(); - const float maxFloatValue = static_cast(maxTargetValue); - - const float windowIntercept = windowCenter - windowWidth / 2.0f; - const float windowSlope = (maxFloatValue + 1.0f) / windowWidth; - - const float a = rescaleSlope * windowSlope; - const float b = (rescaleIntercept - windowIntercept) * windowSlope; - - if (invert) - { - ShiftScaleInternal(target, source, a, b, minTargetValue); - } - else - { - ShiftScaleInternal(target, source, a, b, minTargetValue); - } - } - - void ImageProcessing::ApplyWindowing_Deprecated(ImageAccessor& target, - const ImageAccessor& source, - float windowCenter, - float windowWidth, - float rescaleSlope, - float rescaleIntercept, - bool invert) - { - if (target.GetWidth() != source.GetWidth() || - target.GetHeight() != source.GetHeight()) - { - throw OrthancException(ErrorCode_IncompatibleImageSize); - } - - switch (source.GetFormat()) - { - case Orthanc::PixelFormat_Float32: - { - switch (target.GetFormat()) - { - case Orthanc::PixelFormat_Grayscale8: - ApplyWindowingInternal(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); - break; - case Orthanc::PixelFormat_Grayscale16: - ApplyWindowingInternal(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); - break; - default: - throw OrthancException(ErrorCode_NotImplemented); - } - };break; - case Orthanc::PixelFormat_Grayscale8: - { - switch (target.GetFormat()) - { - case Orthanc::PixelFormat_Grayscale8: - ApplyWindowingInternal(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); - break; - case Orthanc::PixelFormat_Grayscale16: - ApplyWindowingInternal(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); - break; - default: - throw OrthancException(ErrorCode_NotImplemented); - } - };break; - case Orthanc::PixelFormat_Grayscale16: - { - switch (target.GetFormat()) - { - case Orthanc::PixelFormat_Grayscale8: - ApplyWindowingInternal(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); - break; - case Orthanc::PixelFormat_Grayscale16: - ApplyWindowingInternal(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert); - break; - default: - throw OrthancException(ErrorCode_NotImplemented); - } - };break; - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::Convert(ImageAccessor& target, - const ImageAccessor& source) - { - if (target.GetWidth() != source.GetWidth() || - target.GetHeight() != source.GetHeight()) - { - throw OrthancException(ErrorCode_IncompatibleImageSize); - } - - const unsigned int width = source.GetWidth(); - const unsigned int height = source.GetHeight(); - - if (source.GetFormat() == target.GetFormat()) - { - Copy(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale16 && - source.GetFormat() == PixelFormat_Grayscale8) - { - ConvertInternal(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_SignedGrayscale16 && - source.GetFormat() == PixelFormat_Grayscale8) - { - ConvertInternal(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale8 && - source.GetFormat() == PixelFormat_Grayscale16) - { - ConvertInternal(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_SignedGrayscale16 && - source.GetFormat() == PixelFormat_Grayscale16) - { - ConvertInternal(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale8 && - source.GetFormat() == PixelFormat_SignedGrayscale16) - { - ConvertInternal(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale16 && - source.GetFormat() == PixelFormat_SignedGrayscale16) - { - ConvertInternal(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale8 && - source.GetFormat() == PixelFormat_RGB24) - { - ConvertColorToGrayscale(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale16 && - source.GetFormat() == PixelFormat_RGB24) - { - ConvertColorToGrayscale(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_SignedGrayscale16 && - source.GetFormat() == PixelFormat_RGB24) - { - ConvertColorToGrayscale(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Float32 && - source.GetFormat() == PixelFormat_Grayscale8) - { - ConvertGrayscaleToFloat(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Float32 && - source.GetFormat() == PixelFormat_Grayscale16) - { - ConvertGrayscaleToFloat(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Float32 && - source.GetFormat() == PixelFormat_Grayscale32) - { - ConvertGrayscaleToFloat(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Float32 && - source.GetFormat() == PixelFormat_SignedGrayscale16) - { - ConvertGrayscaleToFloat(target, source); - return; - } - - - if (target.GetFormat() == PixelFormat_Grayscale8 && - source.GetFormat() == PixelFormat_RGBA32) - { - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++, q++) - { - *q = static_cast((2126 * static_cast(p[0]) + - 7152 * static_cast(p[1]) + - 0722 * static_cast(p[2])) / 10000); - p += 4; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale8 && - source.GetFormat() == PixelFormat_BGRA32) - { - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++, q++) - { - *q = static_cast((2126 * static_cast(p[2]) + - 7152 * static_cast(p[1]) + - 0722 * static_cast(p[0])) / 10000); - p += 4; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_RGB24 && - source.GetFormat() == PixelFormat_RGBA32) - { - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = p[0]; - q[1] = p[1]; - q[2] = p[2]; - p += 4; - q += 3; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_RGB24 && - source.GetFormat() == PixelFormat_BGRA32) - { - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = p[2]; - q[1] = p[1]; - q[2] = p[0]; - p += 4; - q += 3; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_RGBA32 && - source.GetFormat() == PixelFormat_RGB24) - { - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = p[0]; - q[1] = p[1]; - q[2] = p[2]; - q[3] = 255; // Set the alpha channel to full opacity - p += 3; - q += 4; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_RGB24 && - source.GetFormat() == PixelFormat_Grayscale8) - { - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = *p; - q[1] = *p; - q[2] = *p; - p += 1; - q += 3; - } - } - - return; - } - - if ((target.GetFormat() == PixelFormat_RGBA32 || - target.GetFormat() == PixelFormat_BGRA32) && - source.GetFormat() == PixelFormat_Grayscale8) - { - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = *p; - q[1] = *p; - q[2] = *p; - q[3] = 255; - p += 1; - q += 4; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_BGRA32 && - source.GetFormat() == PixelFormat_Grayscale16) - { - for (unsigned int y = 0; y < height; y++) - { - const uint16_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - uint8_t value = (*p < 256 ? *p : 255); - q[0] = value; - q[1] = value; - q[2] = value; - q[3] = 255; - p += 1; - q += 4; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_BGRA32 && - source.GetFormat() == PixelFormat_SignedGrayscale16) - { - for (unsigned int y = 0; y < height; y++) - { - const int16_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - uint8_t value; - if (*p < 0) - { - value = 0; - } - else if (*p > 255) - { - value = 255; - } - else - { - value = static_cast(*p); - } - - q[0] = value; - q[1] = value; - q[2] = value; - q[3] = 255; - p += 1; - q += 4; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_BGRA32 && - source.GetFormat() == PixelFormat_RGB24) - { - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = p[2]; - q[1] = p[1]; - q[2] = p[0]; - q[3] = 255; - p += 3; - q += 4; - } - } - - return; - } - - if ((target.GetFormat() == PixelFormat_BGRA32 && - source.GetFormat() == PixelFormat_RGBA32) - || (target.GetFormat() == PixelFormat_RGBA32 && - source.GetFormat() == PixelFormat_BGRA32)) - { - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = p[2]; - q[1] = p[1]; - q[2] = p[0]; - q[3] = p[3]; - p += 4; - q += 4; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_RGB24 && - source.GetFormat() == PixelFormat_RGB48) - { - for (unsigned int y = 0; y < height; y++) - { - const uint16_t* p = reinterpret_cast(source.GetConstRow(y)); - uint8_t* q = reinterpret_cast(target.GetRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = p[0] >> 8; - q[1] = p[1] >> 8; - q[2] = p[2] >> 8; - p += 3; - q += 3; - } - } - - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale16 && - source.GetFormat() == PixelFormat_Float32) - { - ConvertFloatToGrayscale(target, source); - return; - } - - if (target.GetFormat() == PixelFormat_Grayscale8 && - source.GetFormat() == PixelFormat_Float32) - { - ConvertFloatToGrayscale(target, source); - return; - } - - throw OrthancException(ErrorCode_NotImplemented); - } - - - - void ImageProcessing::Set(ImageAccessor& image, - int64_t value) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - SetInternal(image, value); - return; - - case PixelFormat_Grayscale16: - SetInternal(image, value); - return; - - case PixelFormat_Grayscale32: - SetInternal(image, value); - return; - - case PixelFormat_Grayscale64: - SetInternal(image, value); - return; - - case PixelFormat_SignedGrayscale16: - SetInternal(image, value); - return; - - case PixelFormat_Float32: - assert(sizeof(float) == 4); - SetInternal(image, value); - return; - - case PixelFormat_RGBA32: - case PixelFormat_BGRA32: - case PixelFormat_RGB24: - { - uint8_t v = static_cast(value); - Set(image, v, v, v, v); // Use the color version - return; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::Set(ImageAccessor& image, - uint8_t red, - uint8_t green, - uint8_t blue, - uint8_t alpha) - { - uint8_t p[4]; - unsigned int size; - - switch (image.GetFormat()) - { - case PixelFormat_RGBA32: - p[0] = red; - p[1] = green; - p[2] = blue; - p[3] = alpha; - size = 4; - break; - - case PixelFormat_BGRA32: - p[0] = blue; - p[1] = green; - p[2] = red; - p[3] = alpha; - size = 4; - break; - - case PixelFormat_RGB24: - p[0] = red; - p[1] = green; - p[2] = blue; - size = 3; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - uint8_t* q = reinterpret_cast(image.GetRow(y)); - - for (unsigned int x = 0; x < width; x++) - { - for (unsigned int i = 0; i < size; i++) - { - q[i] = p[i]; - } - - q += size; - } - } - } - - void ImageProcessing::Set(ImageAccessor& image, - uint8_t red, - uint8_t green, - uint8_t blue, - ImageAccessor& alpha) - { - uint8_t p[4]; - - if (alpha.GetWidth() != image.GetWidth() || alpha.GetHeight() != image.GetHeight()) - { - throw OrthancException(ErrorCode_IncompatibleImageSize); - } - - if (alpha.GetFormat() != PixelFormat_Grayscale8) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - switch (image.GetFormat()) - { - case PixelFormat_RGBA32: - p[0] = red; - p[1] = green; - p[2] = blue; - break; - - case PixelFormat_BGRA32: - p[0] = blue; - p[1] = green; - p[2] = red; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - for (unsigned int y = 0; y < height; y++) - { - uint8_t* q = reinterpret_cast(image.GetRow(y)); - uint8_t* a = reinterpret_cast(alpha.GetRow(y)); - - for (unsigned int x = 0; x < width; x++) - { - for (unsigned int i = 0; i < 3; i++) - { - q[i] = p[i]; - } - q[3] = *a; - q += 4; - ++a; - } - } - } - - - void ImageProcessing::ShiftRight(ImageAccessor& image, - unsigned int shift) - { - if (image.GetWidth() == 0 || - image.GetHeight() == 0 || - shift == 0) - { - // Nothing to do - return; - } - - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - { - ShiftRightInternal(image, shift); - break; - } - - case PixelFormat_Grayscale16: - { - ShiftRightInternal(image, shift); - break; - } - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - void ImageProcessing::ShiftLeft(ImageAccessor& image, - unsigned int shift) - { - if (image.GetWidth() == 0 || - image.GetHeight() == 0 || - shift == 0) - { - // Nothing to do - return; - } - - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - { - ShiftLeftInternal(image, shift); - break; - } - - case PixelFormat_Grayscale16: - { - ShiftLeftInternal(image, shift); - break; - } - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - void ImageProcessing::GetMinMaxIntegerValue(int64_t& minValue, - int64_t& maxValue, - const ImageAccessor& image) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - { - uint8_t a, b; - GetMinMaxValueInternal(a, b, image); - minValue = a; - maxValue = b; - break; - } - - case PixelFormat_Grayscale16: - { - uint16_t a, b; - GetMinMaxValueInternal(a, b, image); - minValue = a; - maxValue = b; - break; - } - - case PixelFormat_Grayscale32: - { - uint32_t a, b; - GetMinMaxValueInternal(a, b, image); - minValue = a; - maxValue = b; - break; - } - - case PixelFormat_SignedGrayscale16: - { - int16_t a, b; - GetMinMaxValueInternal(a, b, image); - minValue = a; - maxValue = b; - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::GetMinMaxFloatValue(float& minValue, - float& maxValue, - const ImageAccessor& image) - { - switch (image.GetFormat()) - { - case PixelFormat_Float32: - { - assert(sizeof(float) == 4); - float a, b; - - /** - * WARNING - On floating-point types, the minimal value is - * "-FLT_MAX" (as implemented by "::lowest()"), not "FLT_MIN" - * (as implemented by "::min()") - * https://en.cppreference.com/w/cpp/types/numeric_limits - **/ - GetMinMaxValueInternal(a, b, image, -std::numeric_limits::max()); - minValue = a; - maxValue = b; - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - - void ImageProcessing::AddConstant(ImageAccessor& image, - int64_t value) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - AddConstantInternal(image, value); - return; - - case PixelFormat_Grayscale16: - AddConstantInternal(image, value); - return; - - case PixelFormat_SignedGrayscale16: - AddConstantInternal(image, value); - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::MultiplyConstant(ImageAccessor& image, - float factor, - bool useRound) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - if (useRound) - { - MultiplyConstantInternal(image, factor); - } - else - { - MultiplyConstantInternal(image, factor); - } - return; - - case PixelFormat_Grayscale16: - if (useRound) - { - MultiplyConstantInternal(image, factor); - } - else - { - MultiplyConstantInternal(image, factor); - } - return; - - case PixelFormat_SignedGrayscale16: - if (useRound) - { - MultiplyConstantInternal(image, factor); - } - else - { - MultiplyConstantInternal(image, factor); - } - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::ShiftScale(ImageAccessor& image, - float offset, - float scaling, - bool useRound) - { - // Rewrite "(x + offset) * scaling" as "a * x + b" - - const float a = scaling; - const float b = offset * scaling; - - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - if (useRound) - { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); - } - else - { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); - } - return; - - case PixelFormat_Grayscale16: - if (useRound) - { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); - } - else - { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); - } - return; - - case PixelFormat_SignedGrayscale16: - if (useRound) - { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); - } - else - { - ShiftScaleInternal(image, image, a, b, std::numeric_limits::min()); - } - return; - - case PixelFormat_Float32: - // "::min()" must be replaced by "::lowest()" or "-::max()" if dealing with float or double. - if (useRound) - { - ShiftScaleInternal(image, image, a, b, -std::numeric_limits::max()); - } - else - { - ShiftScaleInternal(image, image, a, b, -std::numeric_limits::max()); - } - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::ShiftScale(ImageAccessor& target, - const ImageAccessor& source, - float offset, - float scaling, - bool useRound) - { - // Rewrite "(x + offset) * scaling" as "a * x + b" - - const float a = scaling; - const float b = offset * scaling; - - switch (target.GetFormat()) - { - case PixelFormat_Grayscale8: - - switch (source.GetFormat()) - { - case PixelFormat_Float32: - if (useRound) - { - ShiftScaleInternal( - target, source, a, b, std::numeric_limits::min()); - } - else - { - ShiftScaleInternal( - target, source, a, b, std::numeric_limits::min()); - } - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::Invert(ImageAccessor& image, int64_t maxValue) - { - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - switch (image.GetFormat()) - { - case PixelFormat_Grayscale16: - { - uint16_t maxValueUint16 = (uint16_t)(std::min(maxValue, static_cast(std::numeric_limits::max()))); - - for (unsigned int y = 0; y < height; y++) - { - uint16_t* p = reinterpret_cast(image.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++) - { - *p = maxValueUint16 - (*p); - } - } - - return; - } - case PixelFormat_Grayscale8: - { - uint8_t maxValueUint8 = (uint8_t)(std::min(maxValue, static_cast(std::numeric_limits::max()))); - - for (unsigned int y = 0; y < height; y++) - { - uint8_t* p = reinterpret_cast(image.GetRow(y)); - - for (unsigned int x = 0; x < width; x++, p++) - { - *p = maxValueUint8 - (*p); - } - } - - return; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - - } - - void ImageProcessing::Invert(ImageAccessor& image) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - return Invert(image, 255); - default: - throw OrthancException(ErrorCode_NotImplemented); // you should use the Invert(image, maxValue) overload - } - } - - - - namespace - { - template - class BresenhamPixelWriter - { - private: - typedef typename PixelTraits::PixelType PixelType; - - Orthanc::ImageAccessor& image_; - PixelType value_; - - void PlotLineLow(int x0, - int y0, - int x1, - int y1) - { - int dx = x1 - x0; - int dy = y1 - y0; - int yi = 1; - - if (dy < 0) - { - yi = -1; - dy = -dy; - } - - int d = 2 * dy - dx; - int y = y0; - - for (int x = x0; x <= x1; x++) - { - Write(x, y); - - if (d > 0) - { - y = y + yi; - d = d - 2 * dx; - } - - d = d + 2*dy; - } - } - - void PlotLineHigh(int x0, - int y0, - int x1, - int y1) - { - int dx = x1 - x0; - int dy = y1 - y0; - int xi = 1; - - if (dx < 0) - { - xi = -1; - dx = -dx; - } - - int d = 2 * dx - dy; - int x = x0; - - for (int y = y0; y <= y1; y++) - { - Write(x, y); - - if (d > 0) - { - x = x + xi; - d = d - 2 * dy; - } - - d = d + 2 * dx; - } - } - - public: - BresenhamPixelWriter(Orthanc::ImageAccessor& image, - int64_t value) : - image_(image), - value_(PixelTraits::IntegerToPixel(value)) - { - } - - BresenhamPixelWriter(Orthanc::ImageAccessor& image, - const PixelType& value) : - image_(image), - value_(value) - { - } - - void Write(int x, - int y) - { - if (x >= 0 && - y >= 0 && - static_cast(x) < image_.GetWidth() && - static_cast(y) < image_.GetHeight()) - { - PixelType* p = reinterpret_cast(image_.GetRow(y)); - p[x] = value_; - } - } - - void DrawSegment(int x0, - int y0, - int x1, - int y1) - { - // This is an implementation of Bresenham's line algorithm - // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#All_cases - - if (abs(y1 - y0) < abs(x1 - x0)) - { - if (x0 > x1) - { - PlotLineLow(x1, y1, x0, y0); - } - else - { - PlotLineLow(x0, y0, x1, y1); - } - } - else - { - if (y0 > y1) - { - PlotLineHigh(x1, y1, x0, y0); - } - else - { - PlotLineHigh(x0, y0, x1, y1); - } - } - } - }; - } - - - void ImageProcessing::DrawLineSegment(ImageAccessor& image, - int x0, - int y0, - int x1, - int y1, - int64_t value) - { - switch (image.GetFormat()) - { - case Orthanc::PixelFormat_Grayscale8: - { - BresenhamPixelWriter writer(image, value); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - case Orthanc::PixelFormat_Grayscale16: - { - BresenhamPixelWriter writer(image, value); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - case Orthanc::PixelFormat_SignedGrayscale16: - { - BresenhamPixelWriter writer(image, value); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::DrawLineSegment(ImageAccessor& image, - int x0, - int y0, - int x1, - int y1, - uint8_t red, - uint8_t green, - uint8_t blue, - uint8_t alpha) - { - switch (image.GetFormat()) - { - case Orthanc::PixelFormat_BGRA32: - { - PixelTraits::PixelType pixel; - pixel.red_ = red; - pixel.green_ = green; - pixel.blue_ = blue; - pixel.alpha_ = alpha; - - BresenhamPixelWriter writer(image, pixel); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - case Orthanc::PixelFormat_RGBA32: - { - PixelTraits::PixelType pixel; - pixel.red_ = red; - pixel.green_ = green; - pixel.blue_ = blue; - pixel.alpha_ = alpha; - - BresenhamPixelWriter writer(image, pixel); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - case Orthanc::PixelFormat_RGB24: - { - PixelTraits::PixelType pixel; - pixel.red_ = red; - pixel.green_ = green; - pixel.blue_ = blue; - - BresenhamPixelWriter writer(image, pixel); - writer.DrawSegment(x0, y0, x1, y1); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - void ComputePolygonExtent(int32_t& left, int32_t& right, int32_t& top, int32_t& bottom, const std::vector& points) - { - left = std::numeric_limits::max(); - right = std::numeric_limits::min(); - top = std::numeric_limits::max(); - bottom = std::numeric_limits::min(); - - for (size_t i = 0; i < points.size(); i++) - { - const ImageProcessing::ImagePoint& p = points[i]; - left = std::min(p.GetX(), left); - right = std::max(p.GetX(), right); - bottom = std::max(p.GetY(), bottom); - top = std::min(p.GetY(), top); - } - } - - template - void FillPolygon_(ImageAccessor& image, - const std::vector& points, - int64_t value_) - { - typedef typename PixelTraits::PixelType TargetType; - - TargetType value = PixelTraits::IntegerToPixel(value_); - int imageWidth = static_cast(image.GetWidth()); - int imageHeight = static_cast(image.GetHeight()); - int32_t left; - int32_t right; - int32_t top; - int32_t bottom; - - // TODO: test clipping in UT (in Trello board) - ComputePolygonExtent(left, right, top, bottom, points); - - // clip the computed extent with the target image - // L and R - left = std::max(0, left); - left = std::min(imageWidth, left); - right = std::max(0, right); - right = std::min(imageWidth, right); - if (left > right) - std::swap(left, right); - - // T and B - top = std::max(0, top); - top = std::min(imageHeight, top); - bottom = std::max(0, bottom); - bottom = std::min(imageHeight, bottom); - if (top > bottom) - std::swap(top, bottom); - - // from http://alienryderflex.com/polygon_fill/ - - // convert all "corner" points to double only once - std::vector cpx; - std::vector cpy; - size_t cpSize = points.size(); - for (size_t i = 0; i < points.size(); i++) - { - if (points[i].GetX() < 0 || points[i].GetX() >= imageWidth - || points[i].GetY() < 0 || points[i].GetY() >= imageHeight) - { - throw Orthanc::OrthancException(ErrorCode_ParameterOutOfRange); - } - cpx.push_back((double)points[i].GetX()); - cpy.push_back((double)points[i].GetY()); - } - - // Draw the lines segments - for (size_t i = 0; i < (points.size() -1); i++) - { - ImageProcessing::DrawLineSegment(image, points[i].GetX(), points[i].GetY(), points[i+1].GetX(), points[i+1].GetY(), value_); - } - ImageProcessing::DrawLineSegment(image, points[points.size() -1].GetX(), points[points.size() -1].GetY(), points[0].GetX(), points[0].GetY(), value_); - - std::vector nodeX; - nodeX.resize(cpSize); - int nodes, pixelX, pixelY, i, j, swap ; - - // Loop through the rows of the image. - for (pixelY = top; pixelY < bottom; pixelY++) - { - double y = (double)pixelY; - // Build a list of nodes. - nodes = 0; - j = static_cast(cpSize) - 1; - - for (i = 0; i < static_cast(cpSize); i++) - { - if ((cpy[i] < y && cpy[j] >= y) || (cpy[j] < y && cpy[i] >= y)) - { - nodeX[nodes++] = (int32_t)(cpx[i] + (y - cpy[i])/(cpy[j] - cpy[i]) * (cpx[j] - cpx[i])); - } - j=i; - } - - // Sort the nodes, via a simple “Bubble” sort. - i=0; - while (i < nodes-1) - { - if (nodeX[i] > nodeX[i+1]) - { - swap = nodeX[i]; - nodeX[i] = nodeX[i+1]; - nodeX[i+1] = swap; - if (i > 0) - { - i--; - } - } - else - { - i++; - } - } - - TargetType* row = reinterpret_cast(image.GetRow(pixelY)); - // Fill the pixels between node pairs. - for (i = 0; i < nodes; i += 2) - { - if (nodeX[i] >= right) - break; - - if (nodeX[i + 1] >= left) - { - if (nodeX[i] < left) - { - nodeX[i] = left; - } - - if (nodeX[i + 1] > right) - { - nodeX[i + 1] = right; - } - - for (pixelX = nodeX[i]; pixelX <= nodeX[i + 1]; pixelX++) - { - *(row + pixelX) = value; - } - } - } - } - } - - void ImageProcessing::FillPolygon(ImageAccessor& image, - const std::vector& points, - int64_t value) - { - switch (image.GetFormat()) - { - case Orthanc::PixelFormat_Grayscale8: - { - FillPolygon_(image, points, value); - break; - } - case Orthanc::PixelFormat_Grayscale16: - { - FillPolygon_(image, points, value); - break; - } - case Orthanc::PixelFormat_SignedGrayscale16: - { - FillPolygon_(image, points, value); - break; - } - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - template - static void ResizeInternal(ImageAccessor& target, - const ImageAccessor& source) - { - assert(target.GetFormat() == source.GetFormat() && - target.GetFormat() == Format); - - const unsigned int sourceWidth = source.GetWidth(); - const unsigned int sourceHeight = source.GetHeight(); - const unsigned int targetWidth = target.GetWidth(); - const unsigned int targetHeight = target.GetHeight(); - - if (targetWidth == 0 || targetHeight == 0) - { - return; - } - - if (sourceWidth == 0 || sourceHeight == 0) - { - // Avoids division by zero below - ImageProcessing::Set(target, 0); - return; - } - - const float scaleX = static_cast(sourceWidth) / static_cast(targetWidth); - const float scaleY = static_cast(sourceHeight) / static_cast(targetHeight); - - - /** - * Create two lookup tables to quickly know the (x,y) position - * in the source image, given the (x,y) position in the target - * image. - **/ - - std::vector lookupX(targetWidth); - - for (unsigned int x = 0; x < targetWidth; x++) - { - int sourceX = static_cast(std::floor((static_cast(x) + 0.5f) * scaleX)); - if (sourceX < 0) - { - sourceX = 0; // Should never happen - } - else if (sourceX >= static_cast(sourceWidth)) - { - sourceX = sourceWidth - 1; - } - - lookupX[x] = static_cast(sourceX); - } - - std::vector lookupY(targetHeight); - - for (unsigned int y = 0; y < targetHeight; y++) - { - int sourceY = static_cast(std::floor((static_cast(y) + 0.5f) * scaleY)); - if (sourceY < 0) - { - sourceY = 0; // Should never happen - } - else if (sourceY >= static_cast(sourceHeight)) - { - sourceY = sourceHeight - 1; - } - - lookupY[y] = static_cast(sourceY); - } - - - /** - * Actual resizing - **/ - - for (unsigned int targetY = 0; targetY < targetHeight; targetY++) - { - unsigned int sourceY = lookupY[targetY]; - - for (unsigned int targetX = 0; targetX < targetWidth; targetX++) - { - unsigned int sourceX = lookupX[targetX]; - - typename ImageTraits::PixelType pixel; - ImageTraits::GetPixel(pixel, source, sourceX, sourceY); - ImageTraits::SetPixel(target, pixel, targetX, targetY); - } - } - } - - - - void ImageProcessing::Resize(ImageAccessor& target, - const ImageAccessor& source) - { - if (source.GetFormat() != source.GetFormat()) - { - throw OrthancException(ErrorCode_IncompatibleImageFormat); - } - - if (source.GetWidth() == target.GetWidth() && - source.GetHeight() == target.GetHeight()) - { - Copy(target, source); - return; - } - - switch (source.GetFormat()) - { - case PixelFormat_Grayscale8: - ResizeInternal(target, source); - break; - - case PixelFormat_Float32: - ResizeInternal(target, source); - break; - - case PixelFormat_RGB24: - ResizeInternal(target, source); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - ImageAccessor* ImageProcessing::Halve(const ImageAccessor& source, - bool forceMinimalPitch) - { - std::unique_ptr target(new Image(source.GetFormat(), source.GetWidth() / 2, - source.GetHeight() / 2, forceMinimalPitch)); - Resize(*target, source); - return target.release(); - } - - - template - static void FlipXInternal(ImageAccessor& image) - { - const unsigned int height = image.GetHeight(); - const unsigned int width = image.GetWidth(); - - for (unsigned int y = 0; y < height; y++) - { - for (unsigned int x1 = 0; x1 < width / 2; x1++) - { - unsigned int x2 = width - 1 - x1; - - typename ImageTraits::PixelType a, b; - ImageTraits::GetPixel(a, image, x1, y); - ImageTraits::GetPixel(b, image, x2, y); - ImageTraits::SetPixel(image, a, x2, y); - ImageTraits::SetPixel(image, b, x1, y); - } - } - } - - - void ImageProcessing::FlipX(ImageAccessor& image) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - FlipXInternal(image); - break; - - case PixelFormat_RGB24: - FlipXInternal(image); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - template - static void FlipYInternal(ImageAccessor& image) - { - const unsigned int height = image.GetHeight(); - const unsigned int width = image.GetWidth(); - - for (unsigned int y1 = 0; y1 < height / 2; y1++) - { - unsigned int y2 = height - 1 - y1; - - for (unsigned int x = 0; x < width; x++) - { - typename ImageTraits::PixelType a, b; - ImageTraits::GetPixel(a, image, x, y1); - ImageTraits::GetPixel(b, image, x, y2); - ImageTraits::SetPixel(image, a, x, y2); - ImageTraits::SetPixel(image, b, x, y1); - } - } - } - - - void ImageProcessing::FlipY(ImageAccessor& image) - { - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - FlipYInternal(image); - break; - - case PixelFormat_RGB24: - FlipYInternal(image); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - // This is a slow implementation of horizontal convolution on one - // individual channel, that checks for out-of-image values - template - static float GetHorizontalConvolutionFloatSecure(const Orthanc::ImageAccessor& source, - const std::vector& horizontal, - size_t horizontalAnchor, - unsigned int x, - unsigned int y, - float leftBorder, - float rightBorder, - unsigned int channel) - { - const RawPixel* row = reinterpret_cast(source.GetConstRow(y)) + channel; - - float p = 0; - - for (unsigned int k = 0; k < horizontal.size(); k++) - { - float value; - - if (x + k < horizontalAnchor) // Negation of "x - horizontalAnchor + k >= 0" - { - value = leftBorder; - } - else if (x + k >= source.GetWidth() + horizontalAnchor) // Negation of "x - horizontalAnchor + k < width" - { - value = rightBorder; - } - else - { - // The value lies within the image - value = row[(x - horizontalAnchor + k) * ChannelsCount]; - } - - p += value * horizontal[k]; - } - - return p; - } - - - - // This is an implementation of separable convolution that uses - // floating-point arithmetics, and an intermediate Float32 - // image. The out-of-image values are taken as the border - // value. Further optimization is possible. - template - static void SeparableConvolutionFloat(ImageAccessor& image /* inplace */, - const std::vector& horizontal, - size_t horizontalAnchor, - const std::vector& vertical, - size_t verticalAnchor, - float normalization) - { - // WARNING - "::min()" should be replaced by "::lowest()" if - // dealing with float or double (which is not the case so far) - assert(sizeof(RawPixel) <= 2); // Safeguard to remember about "float/double" - - const unsigned int width = image.GetWidth(); - const unsigned int height = image.GetHeight(); - - - /** - * Horizontal convolution - **/ - - Image tmp(PixelFormat_Float32, ChannelsCount * width, height, false); - - for (unsigned int y = 0; y < height; y++) - { - const RawPixel* row = reinterpret_cast(image.GetConstRow(y)); - - float leftBorder[ChannelsCount], rightBorder[ChannelsCount]; - - for (unsigned int c = 0; c < ChannelsCount; c++) - { - leftBorder[c] = row[c]; - rightBorder[c] = row[ChannelsCount * (width - 1) + c]; - } - - float* p = static_cast(tmp.GetRow(y)); - - if (width < horizontal.size()) - { - // It is not possible to have the full kernel within the image, use the direct implementation - for (unsigned int x = 0; x < width; x++) - { - for (unsigned int c = 0; c < ChannelsCount; c++, p++) - { - *p = GetHorizontalConvolutionFloatSecure - (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c); - } - } - } - else - { - // Deal with the left border - for (unsigned int x = 0; x < horizontalAnchor; x++) - { - for (unsigned int c = 0; c < ChannelsCount; c++, p++) - { - *p = GetHorizontalConvolutionFloatSecure - (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c); - } - } - - // Deal with the central portion of the image (all pixel values - // scanned by the kernel lie inside the image) - - for (unsigned int x = 0; x < width - horizontal.size() + 1; x++) - { - for (unsigned int c = 0; c < ChannelsCount; c++, p++) - { - *p = 0; - for (unsigned int k = 0; k < horizontal.size(); k++) - { - *p += static_cast(row[(x + k) * ChannelsCount + c]) * horizontal[k]; - } - } - } - - // Deal with the right border - for (unsigned int x = static_cast( - horizontalAnchor + width - horizontal.size() + 1); x < width; x++) - { - for (unsigned int c = 0; c < ChannelsCount; c++, p++) - { - *p = GetHorizontalConvolutionFloatSecure - (image, horizontal, horizontalAnchor, x, y, leftBorder[c], rightBorder[c], c); - } - } - } - } - - - /** - * Vertical convolution - **/ - - std::vector rows(vertical.size()); - - for (unsigned int y = 0; y < height; y++) - { - for (unsigned int k = 0; k < vertical.size(); k++) - { - if (y + k < verticalAnchor) - { - rows[k] = reinterpret_cast(tmp.GetConstRow(0)); // Use top border - } - else if (y + k >= height + verticalAnchor) - { - rows[k] = reinterpret_cast(tmp.GetConstRow(height - 1)); // Use bottom border - } - else - { - rows[k] = reinterpret_cast(tmp.GetConstRow(static_cast(y + k - verticalAnchor))); - } - } - - RawPixel* p = reinterpret_cast(image.GetRow(y)); - - for (unsigned int x = 0; x < width; x++) - { - for (unsigned int c = 0; c < ChannelsCount; c++, p++) - { - float accumulator = 0; - - for (unsigned int k = 0; k < vertical.size(); k++) - { - accumulator += rows[k][ChannelsCount * x + c] * vertical[k]; - } - - accumulator *= normalization; - - if (accumulator <= static_cast(std::numeric_limits::min())) - { - *p = std::numeric_limits::min(); - } - else if (accumulator >= static_cast(std::numeric_limits::max())) - { - *p = std::numeric_limits::max(); - } - else - { - *p = static_cast(accumulator); - } - } - } - } - } - - - void ImageProcessing::SeparableConvolution(ImageAccessor& image /* inplace */, - const std::vector& horizontal, - size_t horizontalAnchor, - const std::vector& vertical, - size_t verticalAnchor) - { - if (horizontal.size() == 0 || - vertical.size() == 0 || - horizontalAnchor >= horizontal.size() || - verticalAnchor >= vertical.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (image.GetWidth() == 0 || - image.GetHeight() == 0) - { - return; - } - - /** - * Compute normalization - **/ - - float sumHorizontal = 0; - for (size_t i = 0; i < horizontal.size(); i++) - { - sumHorizontal += horizontal[i]; - } - - float sumVertical = 0; - for (size_t i = 0; i < vertical.size(); i++) - { - sumVertical += vertical[i]; - } - - if (fabsf(sumHorizontal) <= std::numeric_limits::epsilon() || - fabsf(sumVertical) <= std::numeric_limits::epsilon()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange, "Singular convolution kernel"); - } - - const float normalization = 1.0f / (sumHorizontal * sumVertical); - - switch (image.GetFormat()) - { - case PixelFormat_Grayscale8: - SeparableConvolutionFloat - (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization); - break; - - case PixelFormat_RGB24: - SeparableConvolutionFloat - (image, horizontal, horizontalAnchor, vertical, verticalAnchor, normalization); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void ImageProcessing::SmoothGaussian5x5(ImageAccessor& image) - { - std::vector kernel(5); - kernel[0] = 1; - kernel[1] = 4; - kernel[2] = 6; - kernel[3] = 4; - kernel[4] = 1; - - SeparableConvolution(image, kernel, 2, kernel, 2); - } - - - void ImageProcessing::FitSize(ImageAccessor& target, - const ImageAccessor& source) - { - if (target.GetWidth() == 0 || - target.GetHeight() == 0) - { - return; - } - - if (source.GetWidth() == target.GetWidth() && - source.GetHeight() == target.GetHeight()) - { - Copy(target, source); - return; - } - - Set(target, 0); - - // Preserve the aspect ratio - float cw = static_cast(source.GetWidth()); - float ch = static_cast(source.GetHeight()); - float r = std::min( - static_cast(target.GetWidth()) / cw, - static_cast(target.GetHeight()) / ch); - - unsigned int sw = std::min(static_cast(boost::math::iround(cw * r)), target.GetWidth()); - unsigned int sh = std::min(static_cast(boost::math::iround(ch * r)), target.GetHeight()); - Image resized(target.GetFormat(), sw, sh, false); - - //ImageProcessing::SmoothGaussian5x5(source); - ImageProcessing::Resize(resized, source); - - assert(target.GetWidth() >= resized.GetWidth() && - target.GetHeight() >= resized.GetHeight()); - unsigned int offsetX = (target.GetWidth() - resized.GetWidth()) / 2; - unsigned int offsetY = (target.GetHeight() - resized.GetHeight()) / 2; - - ImageAccessor region; - target.GetRegion(region, offsetX, offsetY, resized.GetWidth(), resized.GetHeight()); - ImageProcessing::Copy(region, resized); - } - - - ImageAccessor* ImageProcessing::FitSize(const ImageAccessor& source, - unsigned int width, - unsigned int height) - { - std::unique_ptr target(new Image(source.GetFormat(), width, height, false)); - FitSize(*target, source); - return target.release(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/ImageProcessing.h --- a/Core/Images/ImageProcessing.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,197 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../OrthancFramework.h" - -#include "ImageAccessor.h" -#include - -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC ImageProcessing : public boost::noncopyable - { - public: - class ImagePoint - { - int32_t x_; - int32_t y_; - - public: - ImagePoint(int32_t x, int32_t y) - : x_(x), - y_(y) - { - } - - int32_t GetX() const {return x_;} - - int32_t GetY() const {return y_;} - - void Set(int32_t x, int32_t y) - { - x_ = x; - y_ = y; - } - - void ClipTo(int32_t minX, int32_t maxX, int32_t minY, int32_t maxY) - { - x_ = std::max(minX, std::min(maxX, x_)); - y_ = std::max(minY, std::min(maxY, y_)); - } - - double GetDistanceTo(const ImagePoint& other) const; - - double GetDistanceToLine(double a, double b, double c) const; // where ax + by + c = 0 is the equation of the line - }; - - static void Copy(ImageAccessor& target, - const ImageAccessor& source); - - static void Convert(ImageAccessor& target, - const ImageAccessor& source); - - static void ApplyWindowing_Deprecated(ImageAccessor& target, - const ImageAccessor& source, - float windowCenter, - float windowWidth, - float rescaleSlope, - float rescaleIntercept, - bool invert); - - static void Set(ImageAccessor& image, - int64_t value); - - static void Set(ImageAccessor& image, - uint8_t red, - uint8_t green, - uint8_t blue, - uint8_t alpha); - - static void Set(ImageAccessor& image, - uint8_t red, - uint8_t green, - uint8_t blue, - ImageAccessor& alpha); - - static void ShiftRight(ImageAccessor& target, - unsigned int shift); - - static void ShiftLeft(ImageAccessor& target, - unsigned int shift); - - static void GetMinMaxIntegerValue(int64_t& minValue, - int64_t& maxValue, - const ImageAccessor& image); - - static void GetMinMaxFloatValue(float& minValue, - float& maxValue, - const ImageAccessor& image); - - static void AddConstant(ImageAccessor& image, - int64_t value); - - // "useRound" is expensive - static void MultiplyConstant(ImageAccessor& image, - float factor, - bool useRound); - - // Computes "(x + offset) * scaling" inplace. "useRound" is expensive. - static void ShiftScale(ImageAccessor& image, - float offset, - float scaling, - bool useRound); - - static void ShiftScale(ImageAccessor& target, - const ImageAccessor& source, - float offset, - float scaling, - bool useRound); - - static void Invert(ImageAccessor& image); - - static void Invert(ImageAccessor& image, int64_t maxValue); - - static void DrawLineSegment(ImageAccessor& image, - int x0, - int y0, - int x1, - int y1, - int64_t value); - - static void DrawLineSegment(ImageAccessor& image, - int x0, - int y0, - int x1, - int y1, - uint8_t red, - uint8_t green, - uint8_t blue, - uint8_t alpha); - - static void FillPolygon(ImageAccessor& image, - const std::vector& points, - int64_t value); - - static void Resize(ImageAccessor& target, - const ImageAccessor& source); - - static ImageAccessor* Halve(const ImageAccessor& source, - bool forceMinimalPitch); - - static void FlipX(ImageAccessor& image); - - static void FlipY(ImageAccessor& image); - - static void SeparableConvolution(ImageAccessor& image /* inplace */, - const std::vector& horizontal, - size_t horizontalAnchor, - const std::vector& vertical, - size_t verticalAnchor); - - static void SmoothGaussian5x5(ImageAccessor& image); - - static void FitSize(ImageAccessor& target, - const ImageAccessor& source); - - static ImageAccessor* FitSize(const ImageAccessor& source, - unsigned int width, - unsigned int height); - }; -} - diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/ImageTraits.h --- a/Core/Images/ImageTraits.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ImageAccessor.h" -#include "PixelTraits.h" - -#include - -namespace Orthanc -{ - template - struct ImageTraits - { - typedef ::Orthanc::PixelTraits PixelTraits; - typedef typename PixelTraits::PixelType PixelType; - - static PixelFormat GetPixelFormat() - { - return Format; - } - - static void GetPixel(PixelType& target, - const ImageAccessor& image, - unsigned int x, - unsigned int y) - { - assert(x < image.GetWidth() && y < image.GetHeight()); - PixelTraits::Copy(target, image.GetPixelUnchecked(x, y)); - } - - static void SetPixel(ImageAccessor& image, - const PixelType& value, - unsigned int x, - unsigned int y) - { - assert(x < image.GetWidth() && y < image.GetHeight()); - PixelTraits::Copy(image.GetPixelUnchecked(x, y), value); - } - - static float GetFloatPixel(const ImageAccessor& image, - unsigned int x, - unsigned int y) - { - assert(x < image.GetWidth() && y < image.GetHeight()); - return PixelTraits::PixelToFloat(image.GetPixelUnchecked(x, y)); - } - - static void SetFloatPixel(ImageAccessor& image, - float value, - unsigned int x, - unsigned int y) - { - assert(x < image.GetWidth() && y < image.GetHeight()); - PixelTraits::FloatToPixel(image.GetPixelUnchecked(x, y), value); - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/JpegErrorManager.cpp --- a/Core/Images/JpegErrorManager.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,70 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "JpegErrorManager.h" - -namespace Orthanc -{ - namespace Internals - { - void JpegErrorManager::OutputMessage(j_common_ptr cinfo) - { - char message[JMSG_LENGTH_MAX]; - (*cinfo->err->format_message) (cinfo, message); - - JpegErrorManager* that = reinterpret_cast(cinfo->err); - that->message = std::string(message); - } - - - void JpegErrorManager::ErrorExit(j_common_ptr cinfo) - { - (*cinfo->err->output_message) (cinfo); - - JpegErrorManager* that = reinterpret_cast(cinfo->err); - longjmp(that->setjmp_buffer, 1); - } - - - JpegErrorManager::JpegErrorManager() - { - memset(&pub, 0, sizeof(struct jpeg_error_mgr)); - memset(&setjmp_buffer, 0, sizeof(jmp_buf)); - - jpeg_std_error(&pub); - pub.error_exit = ErrorExit; - pub.output_message = OutputMessage; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/JpegErrorManager.h --- a/Core/Images/JpegErrorManager.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - -#pragma once - -#if !defined(ORTHANC_ENABLE_JPEG) -# error The macro ORTHANC_ENABLE_JPEG must be defined -#endif - -#if ORTHANC_ENABLE_JPEG != 1 -# error JPEG support must be enabled to include this file -#endif - -#include -#include -#include -#include -#include - -namespace Orthanc -{ - namespace Internals - { - class JpegErrorManager - { - private: - struct jpeg_error_mgr pub; /* "public" fields */ - jmp_buf setjmp_buffer; /* for return to caller */ - std::string message; - - static void OutputMessage(j_common_ptr cinfo); - - static void ErrorExit(j_common_ptr cinfo); - - public: - JpegErrorManager(); - - struct jpeg_error_mgr* GetPublic() - { - return &pub; - } - - jmp_buf& GetJumpBuffer() - { - return setjmp_buffer; - } - - const std::string& GetMessage() const - { - return message; - } - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/JpegReader.cpp --- a/Core/Images/JpegReader.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,199 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "JpegReader.h" - -#include "JpegErrorManager.h" -#include "../OrthancException.h" -#include "../Logging.h" - -#if ORTHANC_SANDBOXED == 0 -# include "../SystemToolbox.h" -#endif - - -namespace Orthanc -{ - static void Uncompress(struct jpeg_decompress_struct& cinfo, - std::string& content, - ImageAccessor& accessor) - { - // The "static_cast" is necessary on OS X: - // https://github.com/simonfuhrmann/mve/issues/371 - jpeg_read_header(&cinfo, static_cast(true)); - - jpeg_start_decompress(&cinfo); - - PixelFormat format; - if (cinfo.output_components == 1 && - cinfo.out_color_space == JCS_GRAYSCALE) - { - format = PixelFormat_Grayscale8; - } - else if (cinfo.output_components == 3 && - cinfo.out_color_space == JCS_RGB) - { - format = PixelFormat_RGB24; - } - else - { - throw OrthancException(ErrorCode_NotImplemented); - } - - unsigned int pitch = cinfo.output_width * cinfo.output_components; - - /* Make a one-row-high sample array that will go away when done with image */ - JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, pitch, 1); - - try - { - content.resize(pitch * cinfo.output_height); - } - catch (...) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - accessor.AssignWritable(format, cinfo.output_width, cinfo.output_height, pitch, - content.empty() ? NULL : &content[0]); - - uint8_t* target = reinterpret_cast(&content[0]); - while (cinfo.output_scanline < cinfo.output_height) - { - jpeg_read_scanlines(&cinfo, buffer, 1); - memcpy(target, buffer[0], pitch); - target += pitch; - } - - // Everything went fine, "setjmp()" didn't get called - - jpeg_finish_decompress(&cinfo); - } - - -#if ORTHANC_SANDBOXED == 0 - void JpegReader::ReadFromFile(const std::string& filename) - { - FILE* fp = SystemToolbox::OpenFile(filename, FileMode_ReadBinary); - if (!fp) - { - throw OrthancException(ErrorCode_InexistentFile); - } - - struct jpeg_decompress_struct cinfo; - memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct)); - - Internals::JpegErrorManager jerr; - cinfo.err = jerr.GetPublic(); - - if (setjmp(jerr.GetJumpBuffer())) - { - jpeg_destroy_decompress(&cinfo); - fclose(fp); - - throw OrthancException(ErrorCode_InternalError, - "Error during JPEG decoding: " + jerr.GetMessage()); - } - - // Below this line, we are under the scope of a "setjmp" - - jpeg_create_decompress(&cinfo); - jpeg_stdio_src(&cinfo, fp); - - try - { - Uncompress(cinfo, content_, *this); - } - catch (OrthancException&) - { - jpeg_destroy_decompress(&cinfo); - fclose(fp); - throw; - } - - jpeg_destroy_decompress(&cinfo); - fclose(fp); - } -#endif - - - void JpegReader::ReadFromMemory(const void* buffer, - size_t size) - { - struct jpeg_decompress_struct cinfo; - memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct)); - - Internals::JpegErrorManager jerr; - cinfo.err = jerr.GetPublic(); - - if (setjmp(jerr.GetJumpBuffer())) - { - jpeg_destroy_decompress(&cinfo); - throw OrthancException(ErrorCode_InternalError, - "Error during JPEG decoding: " + jerr.GetMessage()); - } - - // Below this line, we are under the scope of a "setjmp" - jpeg_create_decompress(&cinfo); - jpeg_mem_src(&cinfo, - const_cast( - reinterpret_cast(buffer)), - static_cast(size)); - - try - { - Uncompress(cinfo, content_, *this); - } - catch (OrthancException&) - { - jpeg_destroy_decompress(&cinfo); - throw; - } - - jpeg_destroy_decompress(&cinfo); - } - - - void JpegReader::ReadFromMemory(const std::string& buffer) - { - if (buffer.empty()) - { - ReadFromMemory(NULL, 0); - } - else - { - ReadFromMemory(buffer.c_str(), buffer.size()); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/JpegReader.h --- a/Core/Images/JpegReader.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if !defined(ORTHANC_ENABLE_JPEG) -# error The macro ORTHANC_ENABLE_JPEG must be defined -#endif - -#if ORTHANC_ENABLE_JPEG != 1 -# error JPEG support must be enabled to include this file -#endif - -#include "ImageAccessor.h" - -#include - -namespace Orthanc -{ - class JpegReader : public ImageAccessor - { - private: - std::string content_; - - public: -#if ORTHANC_SANDBOXED == 0 - void ReadFromFile(const std::string& filename); -#endif - - void ReadFromMemory(const void* buffer, - size_t size); - - void ReadFromMemory(const std::string& buffer); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/JpegWriter.cpp --- a/Core/Images/JpegWriter.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,213 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "JpegWriter.h" - -#include "../OrthancException.h" -#include "../Logging.h" -#include "JpegErrorManager.h" - -#if ORTHANC_SANDBOXED == 0 -# include "../SystemToolbox.h" -#endif - -#include -#include - -namespace Orthanc -{ - static void GetLines(std::vector& lines, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - if (format != PixelFormat_Grayscale8 && - format != PixelFormat_RGB24) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - lines.resize(height); - - uint8_t* base = const_cast(reinterpret_cast(buffer)); - for (unsigned int y = 0; y < height; y++) - { - lines[y] = base + static_cast(y) * static_cast(pitch); - } - } - - - static void Compress(struct jpeg_compress_struct& cinfo, - std::vector& lines, - unsigned int width, - unsigned int height, - PixelFormat format, - uint8_t quality) - { - cinfo.image_width = width; - cinfo.image_height = height; - - switch (format) - { - case PixelFormat_Grayscale8: - cinfo.input_components = 1; - cinfo.in_color_space = JCS_GRAYSCALE; - break; - - case PixelFormat_RGB24: - cinfo.input_components = 3; - cinfo.in_color_space = JCS_RGB; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - jpeg_set_defaults(&cinfo); - - // The "static_cast" is necessary on OS X: - // https://github.com/simonfuhrmann/mve/issues/371 - jpeg_set_quality(&cinfo, quality, static_cast(true)); - jpeg_start_compress(&cinfo, static_cast(true)); - - jpeg_write_scanlines(&cinfo, &lines[0], height); - jpeg_finish_compress(&cinfo); - jpeg_destroy_compress(&cinfo); - } - - - void JpegWriter::SetQuality(uint8_t quality) - { - if (quality == 0 || quality > 100) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - quality_ = quality; - } - - -#if ORTHANC_SANDBOXED == 0 - void JpegWriter::WriteToFileInternal(const std::string& filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary); - if (fp == NULL) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - - std::vector lines; - GetLines(lines, height, pitch, format, buffer); - - struct jpeg_compress_struct cinfo; - memset(&cinfo, 0, sizeof(struct jpeg_compress_struct)); - - Internals::JpegErrorManager jerr; - cinfo.err = jerr.GetPublic(); - - if (setjmp(jerr.GetJumpBuffer())) - { - /* If we get here, the JPEG code has signaled an error. - * We need to clean up the JPEG object, close the input file, and return. - */ - jpeg_destroy_compress(&cinfo); - fclose(fp); - throw OrthancException(ErrorCode_InternalError, - "Error during JPEG encoding: " + jerr.GetMessage()); - } - - // Do not allocate data on the stack below this line! - - jpeg_create_compress(&cinfo); - jpeg_stdio_dest(&cinfo, fp); - Compress(cinfo, lines, width, height, format, quality_); - - // Everything went fine, "setjmp()" didn't get called - - fclose(fp); - } -#endif - - - void JpegWriter::WriteToMemoryInternal(std::string& jpeg, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - std::vector lines; - GetLines(lines, height, pitch, format, buffer); - - struct jpeg_compress_struct cinfo; - memset(&cinfo, 0, sizeof(struct jpeg_compress_struct)); - - Internals::JpegErrorManager jerr; - - unsigned char* data = NULL; - unsigned long size; - - if (setjmp(jerr.GetJumpBuffer())) - { - jpeg_destroy_compress(&cinfo); - - if (data != NULL) - { - free(data); - } - - throw OrthancException(ErrorCode_InternalError, - "Error during JPEG encoding: " + jerr.GetMessage()); - } - - // Do not allocate data on the stack below this line! - - jpeg_create_compress(&cinfo); - cinfo.err = jerr.GetPublic(); - jpeg_mem_dest(&cinfo, &data, &size); - - Compress(cinfo, lines, width, height, format, quality_); - - // Everything went fine, "setjmp()" didn't get called - - jpeg.assign(reinterpret_cast(data), size); - free(data); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/JpegWriter.h --- a/Core/Images/JpegWriter.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_JPEG) -# error The macro ORTHANC_ENABLE_JPEG must be defined -#endif - -#if ORTHANC_ENABLE_JPEG != 1 -# error JPEG support must be enabled to include this file -#endif - -#include "IImageWriter.h" - -namespace Orthanc -{ - class JpegWriter : public IImageWriter - { - protected: -#if ORTHANC_SANDBOXED == 0 - virtual void WriteToFileInternal(const std::string& filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); -#endif - - virtual void WriteToMemoryInternal(std::string& jpeg, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - private: - uint8_t quality_; - - public: - JpegWriter() : quality_(90) - { - } - - void SetQuality(uint8_t quality); - - uint8_t GetQuality() const - { - return quality_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/PamReader.cpp --- a/Core/Images/PamReader.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,309 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "PamReader.h" - -#include "../Endianness.h" -#include "../OrthancException.h" -#include "../Toolbox.h" - -#if ORTHANC_SANDBOXED == 0 -# include "../SystemToolbox.h" -#endif - -#include // For malloc/free -#include -#include - - -namespace Orthanc -{ - static void GetPixelFormat(PixelFormat& format, - unsigned int& bytesPerChannel, - const unsigned int& maxValue, - const unsigned int& channelCount, - const std::string& tupleType) - { - if (tupleType == "GRAYSCALE" && - channelCount == 1) - { - switch (maxValue) - { - case 255: - format = PixelFormat_Grayscale8; - bytesPerChannel = 1; - return; - - case 65535: - format = PixelFormat_Grayscale16; - bytesPerChannel = 2; - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - else if (tupleType == "RGB" && - channelCount == 3) - { - switch (maxValue) - { - case 255: - format = PixelFormat_RGB24; - bytesPerChannel = 1; - return; - - case 65535: - format = PixelFormat_RGB48; - bytesPerChannel = 2; - return; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - else - { - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - typedef std::map Parameters; - - - static std::string LookupStringParameter(const Parameters& parameters, - const std::string& key) - { - Parameters::const_iterator found = parameters.find(key); - - if (found == parameters.end()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - return found->second; - } - } - - - static unsigned int LookupIntegerParameter(const Parameters& parameters, - const std::string& key) - { - try - { - int value = boost::lexical_cast(LookupStringParameter(parameters, key)); - - if (value < 0) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - return static_cast(value); - } - } - catch (boost::bad_lexical_cast&) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - void PamReader::ParseContent() - { - static const std::string headerDelimiter = "ENDHDR\n"; - - boost::iterator_range headerRange = - boost::algorithm::find_first(content_, headerDelimiter); - - if (!headerRange) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - std::string header(static_cast(content_).begin(), headerRange.begin()); - - std::vector lines; - Toolbox::TokenizeString(lines, header, '\n'); - - if (lines.size() < 2 || - lines.front() != "P7" || - !lines.back().empty()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Parameters parameters; - - for (size_t i = 1; i + 1 < lines.size(); i++) - { - std::vector tokens; - Toolbox::TokenizeString(tokens, lines[i], ' '); - - if (tokens.size() != 2) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - parameters[tokens[0]] = tokens[1]; - } - } - - const unsigned int width = LookupIntegerParameter(parameters, "WIDTH"); - const unsigned int height = LookupIntegerParameter(parameters, "HEIGHT"); - const unsigned int channelCount = LookupIntegerParameter(parameters, "DEPTH"); - const unsigned int maxValue = LookupIntegerParameter(parameters, "MAXVAL"); - const std::string tupleType = LookupStringParameter(parameters, "TUPLTYPE"); - - unsigned int bytesPerChannel; - PixelFormat format; - GetPixelFormat(format, bytesPerChannel, maxValue, channelCount, tupleType.c_str()); - - unsigned int pitch = width * channelCount * bytesPerChannel; - - if (content_.size() != header.size() + headerDelimiter.size() + pitch * height) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - size_t offset = content_.size() - pitch * height; - - { - intptr_t bufferAddr = reinterpret_cast(&content_[offset]); - if((bufferAddr % 8) == 0) - LOG(TRACE) << "PamReader::ParseContent() image address = " << bufferAddr; - else - LOG(TRACE) << "PamReader::ParseContent() image address = " << bufferAddr << " (not a multiple of 8!)"; - } - - // if we want to enforce alignment, we need to use a freshly allocated - // buffer, since we have no alignment guarantees on the original one - if (enforceAligned_) - { - if (alignedImageBuffer_ != NULL) - free(alignedImageBuffer_); - alignedImageBuffer_ = malloc(pitch * height); - memcpy(alignedImageBuffer_, &content_[offset], pitch* height); - content_ = ""; - AssignWritable(format, width, height, pitch, alignedImageBuffer_); - } - else - { - AssignWritable(format, width, height, pitch, &content_[offset]); - } - - // Byte swapping if needed - if (bytesPerChannel != 1 && - bytesPerChannel != 2) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - if (Toolbox::DetectEndianness() == Endianness_Little && - bytesPerChannel == 2) - { - for (unsigned int h = 0; h < height; ++h) - { - uint16_t* pixel = reinterpret_cast(GetRow(h)); - - for (unsigned int w = 0; w < GetWidth(); ++w, ++pixel) - { -#if ORTHANC_ENABLE_WASM == 1 - /* - - crash (2019-08-05): - - Uncaught abort(alignment fault) at Error - at jsStackTrace - at stackTrace - at abort - at alignfault - at SAFE_HEAP_LOAD_i32_2_2 (wasm-function[251132]:39) - at __ZN7Orthanc9PamReader12ParseContentEv (wasm-function[11457]:8088) - - Web Assembly IS LITTLE ENDIAN! - - Perhaps in htobe16 ? - */ - uint8_t* srcdst = reinterpret_cast(pixel); - uint8_t tmp = srcdst[0]; - srcdst[0] = srcdst[1]; - srcdst[1] = tmp; -#else - // memcpy() is necessary to avoid segmentation fault if the - // "pixel" pointer is not 16-bit aligned (which is the case - // if "offset" is an odd number). Check out issue #99: - // https://bitbucket.org/sjodogne/orthanc/issues/99 - uint16_t v = htobe16(*pixel); - memcpy(pixel, &v, sizeof(v)); -#endif - } - } - } - } - - -#if ORTHANC_SANDBOXED == 0 - void PamReader::ReadFromFile(const std::string& filename) - { - SystemToolbox::ReadFile(content_, filename); - ParseContent(); - } -#endif - - - void PamReader::ReadFromMemory(const std::string& buffer) - { - content_ = buffer; - ParseContent(); - } - - void PamReader::ReadFromMemory(const void* buffer, - size_t size) - { - content_.assign(reinterpret_cast(buffer), size); - ParseContent(); - } - - PamReader::~PamReader() - { - if (alignedImageBuffer_ != NULL) - { - free(alignedImageBuffer_); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/PamReader.h --- a/Core/Images/PamReader.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,94 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "ImageAccessor.h" - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -namespace Orthanc -{ - class PamReader : public ImageAccessor - { - private: - void ParseContent(); - - /** - Whether we want to use the default malloc alignment in the image buffer, - at the expense of an extra copy - */ - bool enforceAligned_; - - /** - This is actually a copy of wrappedContent_, but properly aligned. - - It is only used if the enforceAligned parameter is set to true in the - constructor. - */ - void* alignedImageBuffer_; - - /** - Points somewhere in the content_ buffer. - */ - ImageAccessor wrappedContent_; - - /** - Raw content (file bytes or answer from the server, for instance). - */ - std::string content_; - - public: - /** - See doc for field enforceAligned_ - */ - PamReader(bool enforceAligned = false) : - enforceAligned_(enforceAligned), - alignedImageBuffer_(NULL) - { - } - - virtual ~PamReader(); - -#if ORTHANC_SANDBOXED == 0 - void ReadFromFile(const std::string& filename); -#endif - - void ReadFromMemory(const std::string& buffer); - - void ReadFromMemory(const void* buffer, - size_t size); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/PamWriter.cpp --- a/Core/Images/PamWriter.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "PamWriter.h" - -#include "../Endianness.h" -#include "../OrthancException.h" -#include "../Toolbox.h" - -#include - - -namespace Orthanc -{ - static void GetPixelFormatInfo(const PixelFormat& format, - unsigned int& maxValue, - unsigned int& channelCount, - unsigned int& bytesPerChannel, - std::string& tupleType) - { - switch (format) - { - case PixelFormat_Grayscale8: - maxValue = 255; - channelCount = 1; - bytesPerChannel = 1; - tupleType = "GRAYSCALE"; - break; - - case PixelFormat_SignedGrayscale16: - case PixelFormat_Grayscale16: - maxValue = 65535; - channelCount = 1; - bytesPerChannel = 2; - tupleType = "GRAYSCALE"; - break; - - case PixelFormat_RGB24: - maxValue = 255; - channelCount = 3; - bytesPerChannel = 1; - tupleType = "RGB"; - break; - - case PixelFormat_RGB48: - maxValue = 255; - channelCount = 3; - bytesPerChannel = 2; - tupleType = "RGB"; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void PamWriter::WriteToMemoryInternal(std::string& target, - unsigned int width, - unsigned int height, - unsigned int sourcePitch, - PixelFormat format, - const void* buffer) - { - unsigned int maxValue, channelCount, bytesPerChannel; - std::string tupleType; - GetPixelFormatInfo(format, maxValue, channelCount, bytesPerChannel, tupleType); - - target = (std::string("P7") + - std::string("\nWIDTH ") + boost::lexical_cast(width) + - std::string("\nHEIGHT ") + boost::lexical_cast(height) + - std::string("\nDEPTH ") + boost::lexical_cast(channelCount) + - std::string("\nMAXVAL ") + boost::lexical_cast(maxValue) + - std::string("\nTUPLTYPE ") + tupleType + - std::string("\nENDHDR\n")); - - if (bytesPerChannel != 1 && - bytesPerChannel != 2) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - size_t targetPitch = channelCount * bytesPerChannel * width; - size_t offset = target.size(); - - target.resize(offset + targetPitch * height); - - assert(target.size() != 0); - - if (Toolbox::DetectEndianness() == Endianness_Little && - bytesPerChannel == 2) - { - // Byte swapping - for (unsigned int h = 0; h < height; ++h) - { - const uint16_t* p = reinterpret_cast - (reinterpret_cast(buffer) + h * sourcePitch); - uint16_t* q = reinterpret_cast - (reinterpret_cast(&target[offset]) + h * targetPitch); - - for (unsigned int w = 0; w < width * channelCount; ++w) - { - // memcpy() is necessary to avoid segmentation fault if the - // "pixel" pointer is not 16-bit aligned (which is the case - // if "offset" is an odd number). Check out issue #99: - // https://bitbucket.org/sjodogne/orthanc/issues/99 - uint16_t v = htobe16(*p); - memcpy(q, &v, sizeof(uint16_t)); - - p++; - q++; - } - } - } - else - { - // Either "bytesPerChannel == 1" (and endianness is not - // relevant), or we run on a big endian architecture (and no - // byte swapping is necessary, as PAM uses big endian) - - for (unsigned int h = 0; h < height; ++h) - { - const void* p = reinterpret_cast(buffer) + h * sourcePitch; - void* q = reinterpret_cast(&target[offset]) + h * targetPitch; - memcpy(q, p, targetPitch); - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/PamWriter.h --- a/Core/Images/PamWriter.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IImageWriter.h" - -namespace Orthanc -{ - // https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format - class PamWriter : public IImageWriter - { - protected: - virtual void WriteToMemoryInternal(std::string& target, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/PixelTraits.h --- a/Core/Images/PixelTraits.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,412 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" - -#include - -namespace Orthanc -{ - template - struct IntegerPixelTraits - { - typedef _PixelType PixelType; - - ORTHANC_FORCE_INLINE - static PixelFormat GetPixelFormat() - { - return format; - } - - ORTHANC_FORCE_INLINE - static PixelType IntegerToPixel(int64_t value) - { - if (value < static_cast(std::numeric_limits::min())) - { - return std::numeric_limits::min(); - } - else if (value > static_cast(std::numeric_limits::max())) - { - return std::numeric_limits::max(); - } - else - { - return static_cast(value); - } - } - - ORTHANC_FORCE_INLINE - static void SetZero(PixelType& target) - { - target = 0; - } - - ORTHANC_FORCE_INLINE - static void SetMinValue(PixelType& target) - { - target = std::numeric_limits::min(); - } - - ORTHANC_FORCE_INLINE - static void SetMaxValue(PixelType& target) - { - target = std::numeric_limits::max(); - } - - ORTHANC_FORCE_INLINE - static void Copy(PixelType& target, - const PixelType& source) - { - target = source; - } - - ORTHANC_FORCE_INLINE - static float PixelToFloat(const PixelType& source) - { - return static_cast(source); - } - - ORTHANC_FORCE_INLINE - static void FloatToPixel(PixelType& target, - float value) - { - value += 0.5f; - if (value < static_cast(std::numeric_limits::min())) - { - target = std::numeric_limits::min(); - } - else if (value > static_cast(std::numeric_limits::max())) - { - target = std::numeric_limits::max(); - } - else - { - target = static_cast(value); - } - } - - ORTHANC_FORCE_INLINE - static bool IsEqual(const PixelType& a, - const PixelType& b) - { - return a == b; - } - }; - - - template - struct PixelTraits; - - - template <> - struct PixelTraits : - public IntegerPixelTraits - { - }; - - - template <> - struct PixelTraits : - public IntegerPixelTraits - { - }; - - - template <> - struct PixelTraits : - public IntegerPixelTraits - { - }; - - - template <> - struct PixelTraits : - public IntegerPixelTraits - { - }; - - - template <> - struct PixelTraits : - public IntegerPixelTraits - { - }; - - - template <> - struct PixelTraits - { - struct PixelType - { - uint8_t red_; - uint8_t green_; - uint8_t blue_; - }; - - ORTHANC_FORCE_INLINE - static PixelFormat GetPixelFormat() - { - return PixelFormat_RGB24; - } - - ORTHANC_FORCE_INLINE - static void SetZero(PixelType& target) - { - target.red_ = 0; - target.green_ = 0; - target.blue_ = 0; - } - - ORTHANC_FORCE_INLINE - static void Copy(PixelType& target, - const PixelType& source) - { - target.red_ = source.red_; - target.green_ = source.green_; - target.blue_ = source.blue_; - } - - ORTHANC_FORCE_INLINE - static bool IsEqual(const PixelType& a, - const PixelType& b) - { - return (a.red_ == b.red_ && - a.green_ == b.green_ && - a.blue_ == b.blue_); - } - - ORTHANC_FORCE_INLINE - static void FloatToPixel(PixelType& target, - float value) - { - uint8_t v; - PixelTraits::FloatToPixel(v, value); - - target.red_ = v; - target.green_ = v; - target.blue_ = v; - } - }; - - - template <> - struct PixelTraits - { - struct PixelType - { - uint8_t blue_; - uint8_t green_; - uint8_t red_; - uint8_t alpha_; - }; - - ORTHANC_FORCE_INLINE - static PixelFormat GetPixelFormat() - { - return PixelFormat_BGRA32; - } - - ORTHANC_FORCE_INLINE - static void SetZero(PixelType& target) - { - target.blue_ = 0; - target.green_ = 0; - target.red_ = 0; - target.alpha_ = 0; - } - - ORTHANC_FORCE_INLINE - static void Copy(PixelType& target, - const PixelType& source) - { - target.blue_ = source.blue_; - target.green_ = source.green_; - target.red_ = source.red_; - target.alpha_ = source.alpha_; - } - - ORTHANC_FORCE_INLINE - static bool IsEqual(const PixelType& a, - const PixelType& b) - { - return (a.blue_ == b.blue_ && - a.green_ == b.green_ && - a.red_ == b.red_ && - a.alpha_ == b.alpha_); - } - - ORTHANC_FORCE_INLINE - static void FloatToPixel(PixelType& target, - float value) - { - uint8_t v; - PixelTraits::FloatToPixel(v, value); - - target.blue_ = v; - target.green_ = v; - target.red_ = v; - target.alpha_ = 255; - } - }; - - - template <> - struct PixelTraits - { - struct PixelType - { - uint8_t red_; - uint8_t green_; - uint8_t blue_; - uint8_t alpha_; - }; - - ORTHANC_FORCE_INLINE - static PixelFormat GetPixelFormat() - { - return PixelFormat_RGBA32; - } - - ORTHANC_FORCE_INLINE - static void SetZero(PixelType& target) - { - target.red_ = 0; - target.green_ = 0; - target.blue_ = 0; - target.alpha_ = 0; - } - - ORTHANC_FORCE_INLINE - static void Copy(PixelType& target, - const PixelType& source) - { - target.red_ = source.red_; - target.green_ = source.green_; - target.blue_ = source.blue_; - target.alpha_ = source.alpha_; - } - - ORTHANC_FORCE_INLINE - static bool IsEqual(const PixelType& a, - const PixelType& b) - { - return (a.red_ == b.red_ && - a.green_ == b.green_ && - a.blue_ == b.blue_ && - a.alpha_ == b.alpha_); - } - - ORTHANC_FORCE_INLINE - static void FloatToPixel(PixelType& target, - float value) - { - uint8_t v; - PixelTraits::FloatToPixel(v, value); - - target.red_ = v; - target.green_ = v; - target.blue_ = v; - target.alpha_ = 255; - } - }; - - - template <> - struct PixelTraits - { - typedef float PixelType; - - ORTHANC_FORCE_INLINE - static PixelFormat GetPixelFormat() - { - return PixelFormat_Float32; - } - - ORTHANC_FORCE_INLINE - static void SetZero(PixelType& target) - { - target = 0.0f; - } - - ORTHANC_FORCE_INLINE - static void Copy(PixelType& target, - const PixelType& source) - { - target = source; - } - - ORTHANC_FORCE_INLINE - static bool IsEqual(const PixelType& a, - const PixelType& b) - { - float tmp = (a - b); - - if (tmp < 0) - { - tmp = -tmp; - } - - return tmp <= std::numeric_limits::epsilon(); - } - - ORTHANC_FORCE_INLINE - static void SetMinValue(PixelType& target) - { - // std::numeric_limits::lowest is not supported on - // all compilers (for instance, Visual Studio 9.0 2008) - target = -std::numeric_limits::max(); - } - - ORTHANC_FORCE_INLINE - static void SetMaxValue(PixelType& target) - { - target = std::numeric_limits::max(); - } - - ORTHANC_FORCE_INLINE - static void FloatToPixel(PixelType& target, - float value) - { - target = value; - } - - ORTHANC_FORCE_INLINE - static float PixelToFloat(const PixelType& source) - { - return source; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/PngReader.cpp --- a/Core/Images/PngReader.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,325 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "PngReader.h" - -#include "../OrthancException.h" -#include "../Toolbox.h" - -#if ORTHANC_SANDBOXED == 0 -# include "../SystemToolbox.h" -#endif - -#include -#include // For memcpy() - -namespace Orthanc -{ -#if ORTHANC_SANDBOXED == 0 - namespace - { - struct FileRabi - { - FILE* fp_; - - FileRabi(const char* filename) - { - fp_ = SystemToolbox::OpenFile(filename, FileMode_ReadBinary); - if (!fp_) - { - throw OrthancException(ErrorCode_InexistentFile); - } - } - - ~FileRabi() - { - if (fp_) - { - fclose(fp_); - } - } - }; - } -#endif - - - struct PngReader::PngRabi - { - png_structp png_; - png_infop info_; - png_infop endInfo_; - - void Destruct() - { - if (png_) - { - png_destroy_read_struct(&png_, &info_, &endInfo_); - - png_ = NULL; - info_ = NULL; - endInfo_ = NULL; - } - } - - PngRabi() - { - png_ = NULL; - info_ = NULL; - endInfo_ = NULL; - - png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png_) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - info_ = png_create_info_struct(png_); - if (!info_) - { - png_destroy_read_struct(&png_, NULL, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - endInfo_ = png_create_info_struct(png_); - if (!info_) - { - png_destroy_read_struct(&png_, &info_, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - ~PngRabi() - { - Destruct(); - } - - static void MemoryCallback(png_structp png_ptr, - png_bytep data, - png_size_t size); - }; - - - void PngReader::CheckHeader(const void* header) - { - int is_png = !png_sig_cmp((png_bytep) header, 0, 8); - if (!is_png) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - PngReader::PngReader() - { - } - - void PngReader::Read(PngRabi& rabi) - { - png_set_sig_bytes(rabi.png_, 8); - - png_read_info(rabi.png_, rabi.info_); - - png_uint_32 width, height; - int bit_depth, color_type, interlace_type; - int compression_type, filter_method; - // get size and bit-depth of the PNG-image - png_get_IHDR(rabi.png_, rabi.info_, - &width, &height, - &bit_depth, &color_type, &interlace_type, - &compression_type, &filter_method); - - PixelFormat format; - unsigned int pitch; - - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8) - { - format = PixelFormat_Grayscale8; - pitch = width; - } - else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16) - { - format = PixelFormat_Grayscale16; - pitch = 2 * width; - - if (Toolbox::DetectEndianness() == Endianness_Little) - { - png_set_swap(rabi.png_); - } - } - else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8) - { - format = PixelFormat_RGB24; - pitch = 3 * width; - } - else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8) - { - format = PixelFormat_RGBA32; - pitch = 4 * width; - } - else - { - throw OrthancException(ErrorCode_NotImplemented); - } - - data_.resize(height * pitch); - - if (height == 0 || width == 0) - { - // Empty image, we are done - AssignEmpty(format); - return; - } - - png_read_update_info(rabi.png_, rabi.info_); - - std::vector rows(height); - for (size_t i = 0; i < height; i++) - { - rows[i] = &data_[0] + i * pitch; - } - - png_read_image(rabi.png_, &rows[0]); - - AssignWritable(format, width, height, pitch, &data_[0]); - } - - -#if ORTHANC_SANDBOXED == 0 - void PngReader::ReadFromFile(const std::string& filename) - { - FileRabi f(filename.c_str()); - - char header[8]; - if (fread(header, 1, 8, f.fp_) != 8) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - CheckHeader(header); - - PngRabi rabi; - - if (setjmp(png_jmpbuf(rabi.png_))) - { - rabi.Destruct(); - throw OrthancException(ErrorCode_BadFileFormat); - } - - png_init_io(rabi.png_, f.fp_); - - Read(rabi); - } -#endif - - - namespace - { - struct MemoryBuffer - { - const uint8_t* buffer_; - size_t size_; - size_t pos_; - bool ok_; - }; - } - - - void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, - png_bytep outBytes, - png_size_t byteCountToRead) - { - MemoryBuffer* from = reinterpret_cast(png_get_io_ptr(png_ptr)); - - if (!from->ok_) - { - return; - } - - if (from->pos_ + byteCountToRead > from->size_) - { - from->ok_ = false; - return; - } - - memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead); - - from->pos_ += byteCountToRead; - } - - - void PngReader::ReadFromMemory(const void* buffer, - size_t size) - { - if (size < 8) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - CheckHeader(buffer); - - PngRabi rabi; - - if (setjmp(png_jmpbuf(rabi.png_))) - { - rabi.Destruct(); - throw OrthancException(ErrorCode_BadFileFormat); - } - - MemoryBuffer tmp; - tmp.buffer_ = reinterpret_cast(buffer) + 8; // We skip the header - tmp.size_ = size - 8; - tmp.pos_ = 0; - tmp.ok_ = true; - - png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback); - - Read(rabi); - - if (!tmp.ok_) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - void PngReader::ReadFromMemory(const std::string& buffer) - { - if (buffer.size() != 0) - { - ReadFromMemory(&buffer[0], buffer.size()); - } - else - { - ReadFromMemory(NULL, 0); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/PngReader.h --- a/Core/Images/PngReader.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_PNG) -# error The macro ORTHANC_ENABLE_PNG must be defined -#endif - -#if ORTHANC_ENABLE_PNG != 1 -# error PNG support must be enabled to include this file -#endif - -#include "ImageAccessor.h" - -#include "../Enumerations.h" - -#include -#include -#include - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -namespace Orthanc -{ - class PngReader : public ImageAccessor - { - private: - struct PngRabi; - - std::vector data_; - - void CheckHeader(const void* header); - - void Read(PngRabi& rabi); - - public: - PngReader(); - -#if ORTHANC_SANDBOXED == 0 - void ReadFromFile(const std::string& filename); -#endif - - void ReadFromMemory(const void* buffer, - size_t size); - - void ReadFromMemory(const std::string& buffer); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/PngWriter.cpp --- a/Core/Images/PngWriter.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,274 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "PngWriter.h" - -#include -#include -#include -#include "../OrthancException.h" -#include "../ChunkedBuffer.h" -#include "../Toolbox.h" - -#if ORTHANC_SANDBOXED == 0 -# include "../SystemToolbox.h" -#endif - - -// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4 -// http://zarb.org/~gc/html/libpng.html -/* - void write_row_callback(png_ptr, png_uint_32 row, int pass) - { - }*/ - - - - -/* bool isError_; - -// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2 - -static void ErrorHandler(png_structp png, png_const_charp message) -{ -printf("** [%s]\n", message); - -PngWriter* that = (PngWriter*) png_get_error_ptr(png); -that->isError_ = true; -printf("** %d\n", (int)that); - -//((PngWriter*) payload)->isError_ = true; -} - -static void WarningHandler(png_structp png, png_const_charp message) -{ - printf("++ %d\n", (int)message); -}*/ - - -namespace Orthanc -{ - struct PngWriter::PImpl - { - png_structp png_; - png_infop info_; - - // Filled by Prepare() - std::vector rows_; - int bitDepth_; - int colorType_; - }; - - - - PngWriter::PngWriter() : pimpl_(new PImpl) - { - pimpl_->png_ = NULL; - pimpl_->info_ = NULL; - - pimpl_->png_ = png_create_write_struct - (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler); - if (!pimpl_->png_) - { - throw OrthancException(ErrorCode_NotEnoughMemory); - } - - pimpl_->info_ = png_create_info_struct(pimpl_->png_); - if (!pimpl_->info_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - throw OrthancException(ErrorCode_NotEnoughMemory); - } - } - - PngWriter::~PngWriter() - { - if (pimpl_->info_) - { - png_destroy_info_struct(pimpl_->png_, &pimpl_->info_); - } - - if (pimpl_->png_) - { - png_destroy_write_struct(&pimpl_->png_, NULL); - } - } - - - - void PngWriter::Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - pimpl_->rows_.resize(height); - for (unsigned int y = 0; y < height; y++) - { - pimpl_->rows_[y] = const_cast(reinterpret_cast(buffer)) + y * pitch; - } - - switch (format) - { - case PixelFormat_RGB24: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_RGB; - break; - - case PixelFormat_RGBA32: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA; - break; - - case PixelFormat_Grayscale8: - pimpl_->bitDepth_ = 8; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - pimpl_->bitDepth_ = 16; - pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY; - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void PngWriter::Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format) - { - png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height, - pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - png_write_info(pimpl_->png_, pimpl_->info_); - - if (height > 0) - { - switch (format) - { - case PixelFormat_Grayscale16: - case PixelFormat_SignedGrayscale16: - { - int transforms = 0; - if (Toolbox::DetectEndianness() == Endianness_Little) - { - transforms = PNG_TRANSFORM_SWAP_ENDIAN; - } - - png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]); - png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL); - - break; - } - - default: - png_write_image(pimpl_->png_, &pimpl_->rows_[0]); - } - } - - png_write_end(pimpl_->png_, NULL); - } - - -#if ORTHANC_SANDBOXED == 0 - void PngWriter::WriteToFileInternal(const std::string& filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - Prepare(width, height, pitch, format, buffer); - - FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary); - if (!fp) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - - png_init_io(pimpl_->png_, fp); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_CannotWriteFile); - } - - Compress(width, height, pitch, format); - - fclose(fp); - } -#endif - - - static void MemoryCallback(png_structp png_ptr, - png_bytep data, - png_size_t size) - { - ChunkedBuffer* buffer = reinterpret_cast(png_get_io_ptr(png_ptr)); - buffer->AddChunk(data, size); - } - - - - void PngWriter::WriteToMemoryInternal(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer) - { - ChunkedBuffer chunks; - - Prepare(width, height, pitch, format, buffer); - - if (setjmp(png_jmpbuf(pimpl_->png_))) - { - // Error during writing PNG - throw OrthancException(ErrorCode_InternalError); - } - - png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL); - - Compress(width, height, pitch, format); - - chunks.Flatten(png); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Images/PngWriter.h --- a/Core/Images/PngWriter.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_PNG) -# error The macro ORTHANC_ENABLE_PNG must be defined -#endif - -#if ORTHANC_ENABLE_PNG != 1 -# error PNG support must be enabled to include this file -#endif - -#include "IImageWriter.h" - -#include - -namespace Orthanc -{ - class PngWriter : public IImageWriter - { - protected: -#if ORTHANC_SANDBOXED == 0 - virtual void WriteToFileInternal(const std::string& filename, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); -#endif - - virtual void WriteToMemoryInternal(std::string& png, - unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - private: - struct PImpl; - boost::shared_ptr pimpl_; - - void Compress(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format); - - void Prepare(unsigned int width, - unsigned int height, - unsigned int pitch, - PixelFormat format, - const void* buffer); - - public: - PngWriter(); - - ~PngWriter(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/GenericJobUnserializer.cpp --- a/Core/JobsEngine/GenericJobUnserializer.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "GenericJobUnserializer.h" - -#include "../Logging.h" -#include "../OrthancException.h" -#include "../SerializationToolbox.h" - -#include "Operations/LogJobOperation.h" -#include "Operations/NullOperationValue.h" -#include "Operations/SequenceOfOperationsJob.h" -#include "Operations/StringOperationValue.h" - -namespace Orthanc -{ - IJob* GenericJobUnserializer::UnserializeJob(const Json::Value& source) - { - const std::string type = SerializationToolbox::ReadString(source, "Type"); - - if (type == "SequenceOfOperations") - { - return new SequenceOfOperationsJob(*this, source); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot unserialize job of type: " + type); - } - } - - - IJobOperation* GenericJobUnserializer::UnserializeOperation(const Json::Value& source) - { - const std::string type = SerializationToolbox::ReadString(source, "Type"); - - if (type == "Log") - { - return new LogJobOperation; - } - else - { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot unserialize operation of type: " + type); - } - } - - - JobOperationValue* GenericJobUnserializer::UnserializeValue(const Json::Value& source) - { - const std::string type = SerializationToolbox::ReadString(source, "Type"); - - if (type == "String") - { - return new StringOperationValue(SerializationToolbox::ReadString(source, "Content")); - } - else if (type == "Null") - { - return new NullOperationValue; - } - else - { - throw OrthancException(ErrorCode_BadFileFormat, - "Cannot unserialize value of type: " + type); - } - } -} - diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/GenericJobUnserializer.h --- a/Core/JobsEngine/GenericJobUnserializer.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IJobUnserializer.h" - -namespace Orthanc -{ - class GenericJobUnserializer : public IJobUnserializer - { - public: - virtual IJob* UnserializeJob(const Json::Value& value); - - virtual IJobOperation* UnserializeOperation(const Json::Value& value); - - virtual JobOperationValue* UnserializeValue(const Json::Value& value); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/IJob.h --- a/Core/JobsEngine/IJob.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "JobStepResult.h" - -#include -#include - -namespace Orthanc -{ - class IJob : public boost::noncopyable - { - public: - virtual ~IJob() - { - } - - // Method called once the job enters the jobs engine - virtual void Start() = 0; - - virtual JobStepResult Step(const std::string& jobId) = 0; - - // Method called once the job is resubmitted after a failure - virtual void Reset() = 0; - - // For pausing/canceling/ending jobs: This method must release allocated resources - virtual void Stop(JobStopReason reason) = 0; - - virtual float GetProgress() = 0; - - virtual void GetJobType(std::string& target) = 0; - - virtual void GetPublicContent(Json::Value& value) = 0; - - virtual bool Serialize(Json::Value& value) = 0; - - // This function can only be called if the job has reached its - // "success" state - virtual bool GetOutput(std::string& output, - MimeType& mime, - const std::string& key) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/IJobUnserializer.h --- a/Core/JobsEngine/IJobUnserializer.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IJob.h" -#include "Operations/JobOperationValue.h" -#include "Operations/IJobOperation.h" - -#include - -namespace Orthanc -{ - class IJobUnserializer : public boost::noncopyable - { - public: - virtual ~IJobUnserializer() - { - } - - virtual IJob* UnserializeJob(const Json::Value& value) = 0; - - virtual IJobOperation* UnserializeOperation(const Json::Value& value) = 0; - - virtual JobOperationValue* UnserializeValue(const Json::Value& value) = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobInfo.cpp --- a/Core/JobsEngine/JobInfo.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" - -#ifdef __EMSCRIPTEN__ -/* -Avoid this error: - -.../boost/math/special_functions/round.hpp:118:12: warning: implicit conversion from 'std::__2::numeric_limits::type' (aka 'long long') to 'float' changes value from 9223372036854775807 to 9223372036854775808 [-Wimplicit-int-float-conversion] -.../boost/math/special_functions/round.hpp:125:11: note: in instantiation of function template specialization 'boost::math::llround >' requested here -.../orthanc/Core/JobsEngine/JobInfo.cpp:69:44: note: in instantiation of function template specialization 'boost::math::llround' requested here - -.../boost/math/special_functions/round.hpp:86:12: warning: implicit conversion from 'std::__2::numeric_limits::type' (aka 'int') to 'float' changes value from 2147483647 to 2147483648 [-Wimplicit-int-float-conversion] -.../boost/math/special_functions/round.hpp:93:11: note: in instantiation of function template specialization 'boost::math::iround >' requested here -.../orthanc/Core/JobsEngine/JobInfo.cpp:133:39: note: in instantiation of function template specialization 'boost::math::iround' requested here -*/ -#pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" -#endif - -#include "JobInfo.h" - -#include "../OrthancException.h" - -// This "include" is mandatory for Release builds using Linux Standard Base -#include - -namespace Orthanc -{ - JobInfo::JobInfo(const std::string& id, - int priority, - JobState state, - const JobStatus& status, - const boost::posix_time::ptime& creationTime, - const boost::posix_time::ptime& lastStateChangeTime, - const boost::posix_time::time_duration& runtime) : - id_(id), - priority_(priority), - state_(state), - timestamp_(boost::posix_time::microsec_clock::universal_time()), - creationTime_(creationTime), - lastStateChangeTime_(lastStateChangeTime), - runtime_(runtime), - hasEta_(false), - status_(status) - { - if (state_ == JobState_Running) - { - float ms = static_cast(runtime_.total_milliseconds()); - - if (status_.GetProgress() > 0.01f && - ms > 0.01f) - { - float ratio = static_cast(1.0 - status_.GetProgress()); - long long remaining = boost::math::llround(ratio * ms); - eta_ = timestamp_ + boost::posix_time::milliseconds(remaining); - hasEta_ = true; - } - } - } - - - JobInfo::JobInfo() : - priority_(0), - state_(JobState_Failure), - timestamp_(boost::posix_time::microsec_clock::universal_time()), - creationTime_(timestamp_), - lastStateChangeTime_(timestamp_), - runtime_(boost::posix_time::milliseconds(0)), - hasEta_(false) - { - } - - - bool JobInfo::HasCompletionTime() const - { - return (state_ == JobState_Success || - state_ == JobState_Failure); - } - - - const boost::posix_time::ptime& JobInfo::GetEstimatedTimeOfArrival() const - { - if (hasEta_) - { - return eta_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - const boost::posix_time::ptime& JobInfo::GetCompletionTime() const - { - if (HasCompletionTime()) - { - return lastStateChangeTime_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - void JobInfo::Format(Json::Value& target) const - { - target = Json::objectValue; - target["ID"] = id_; - target["Priority"] = priority_; - target["ErrorCode"] = static_cast(status_.GetErrorCode()); - target["ErrorDescription"] = EnumerationToString(status_.GetErrorCode()); - target["State"] = EnumerationToString(state_); - target["Timestamp"] = boost::posix_time::to_iso_string(timestamp_); - target["CreationTime"] = boost::posix_time::to_iso_string(creationTime_); - target["EffectiveRuntime"] = static_cast(runtime_.total_milliseconds()) / 1000.0; - target["Progress"] = boost::math::iround(status_.GetProgress() * 100.0f); - - target["Type"] = status_.GetJobType(); - target["Content"] = status_.GetPublicContent(); - - if (HasEstimatedTimeOfArrival()) - { - target["EstimatedTimeOfArrival"] = boost::posix_time::to_iso_string(GetEstimatedTimeOfArrival()); - } - - if (HasCompletionTime()) - { - target["CompletionTime"] = boost::posix_time::to_iso_string(GetCompletionTime()); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobInfo.h --- a/Core/JobsEngine/JobInfo.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,120 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "JobStatus.h" - -#include - -namespace Orthanc -{ - class JobInfo - { - private: - std::string id_; - int priority_; - JobState state_; - boost::posix_time::ptime timestamp_; - boost::posix_time::ptime creationTime_; - boost::posix_time::ptime lastStateChangeTime_; - boost::posix_time::time_duration runtime_; - bool hasEta_; - boost::posix_time::ptime eta_; - JobStatus status_; - - public: - JobInfo(const std::string& id, - int priority, - JobState state, - const JobStatus& status, - const boost::posix_time::ptime& creationTime, - const boost::posix_time::ptime& lastStateChangeTime, - const boost::posix_time::time_duration& runtime); - - JobInfo(); - - const std::string& GetIdentifier() const - { - return id_; - } - - int GetPriority() const - { - return priority_; - } - - JobState GetState() const - { - return state_; - } - - const boost::posix_time::ptime& GetInfoTime() const - { - return timestamp_; - } - - const boost::posix_time::ptime& GetCreationTime() const - { - return creationTime_; - } - - const boost::posix_time::time_duration& GetRuntime() const - { - return runtime_; - } - - bool HasEstimatedTimeOfArrival() const - { - return hasEta_; - } - - bool HasCompletionTime() const; - - const boost::posix_time::ptime& GetEstimatedTimeOfArrival() const; - - const boost::posix_time::ptime& GetCompletionTime() const; - - const JobStatus& GetStatus() const - { - return status_; - } - - JobStatus& GetStatus() - { - return status_; - } - - void Format(Json::Value& target) const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobStatus.cpp --- a/Core/JobsEngine/JobStatus.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "JobStatus.h" - -#include "../OrthancException.h" - -namespace Orthanc -{ - JobStatus::JobStatus() : - errorCode_(ErrorCode_InternalError), - progress_(0), - jobType_("Invalid"), - publicContent_(Json::objectValue), - hasSerialized_(false) - { - } - - - JobStatus::JobStatus(ErrorCode code, - const std::string& details, - IJob& job) : - errorCode_(code), - progress_(job.GetProgress()), - publicContent_(Json::objectValue), - details_(details) - { - if (progress_ < 0) - { - progress_ = 0; - } - - if (progress_ > 1) - { - progress_ = 1; - } - - job.GetJobType(jobType_); - job.GetPublicContent(publicContent_); - - hasSerialized_ = job.Serialize(serialized_); - } - - - const Json::Value& JobStatus::GetSerialized() const - { - if (!hasSerialized_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return serialized_; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobStatus.h --- a/Core/JobsEngine/JobStatus.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IJob.h" - -namespace Orthanc -{ - class JobStatus - { - private: - ErrorCode errorCode_; - float progress_; - std::string jobType_; - Json::Value publicContent_; - Json::Value serialized_; - bool hasSerialized_; - std::string details_; - - public: - JobStatus(); - - JobStatus(ErrorCode code, - const std::string& details, - IJob& job); - - ErrorCode GetErrorCode() const - { - return errorCode_; - } - - void SetErrorCode(ErrorCode error) - { - errorCode_ = error; - } - - float GetProgress() const - { - return progress_; - } - - const std::string& GetJobType() const - { - return jobType_; - } - - const Json::Value& GetPublicContent() const - { - return publicContent_; - } - - const Json::Value& GetSerialized() const; - - bool HasSerialized() const - { - return hasSerialized_; - } - - const std::string& GetDetails() const - { - return details_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobStepResult.cpp --- a/Core/JobsEngine/JobStepResult.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "JobStepResult.h" - -#include "../OrthancException.h" - -namespace Orthanc -{ - JobStepResult JobStepResult::Retry(unsigned int timeout) - { - JobStepResult result(JobStepCode_Retry); - result.timeout_ = timeout; - return result; - } - - - JobStepResult JobStepResult::Failure(const ErrorCode& error, - const char* details) - { - JobStepResult result(JobStepCode_Failure); - result.error_ = error; - - if (details != NULL) - { - result.failureDetails_ = details; - } - - return result; - } - - - JobStepResult JobStepResult::Failure(const OrthancException& exception) - { - return Failure(exception.GetErrorCode(), - exception.HasDetails() ? exception.GetDetails() : NULL); - } - - - unsigned int JobStepResult::GetRetryTimeout() const - { - if (code_ == JobStepCode_Retry) - { - return timeout_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - ErrorCode JobStepResult::GetFailureCode() const - { - if (code_ == JobStepCode_Failure) - { - return error_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - const std::string& JobStepResult::GetFailureDetails() const - { - if (code_ == JobStepCode_Failure) - { - return failureDetails_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobStepResult.h --- a/Core/JobsEngine/JobStepResult.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Enumerations.h" - -namespace Orthanc -{ - class OrthancException; - - class JobStepResult - { - private: - JobStepCode code_; - unsigned int timeout_; - ErrorCode error_; - std::string failureDetails_; - - explicit JobStepResult(JobStepCode code) : - code_(code), - timeout_(0), - error_(ErrorCode_Success) - { - } - - public: - explicit JobStepResult() : - code_(JobStepCode_Failure), - timeout_(0), - error_(ErrorCode_InternalError) - { - } - - static JobStepResult Success() - { - return JobStepResult(JobStepCode_Success); - } - - static JobStepResult Continue() - { - return JobStepResult(JobStepCode_Continue); - } - - static JobStepResult Retry(unsigned int timeout); - - static JobStepResult Failure(const ErrorCode& error, - const char* details); - - static JobStepResult Failure(const OrthancException& exception); - - JobStepCode GetCode() const - { - return code_; - } - - unsigned int GetRetryTimeout() const; - - ErrorCode GetFailureCode() const; - - const std::string& GetFailureDetails() const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobsEngine.cpp --- a/Core/JobsEngine/JobsEngine.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,326 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "JobsEngine.h" - -#include "../Logging.h" -#include "../OrthancException.h" - -#include - -namespace Orthanc -{ - bool JobsEngine::IsRunning() - { - boost::mutex::scoped_lock lock(stateMutex_); - return (state_ == State_Running); - } - - - bool JobsEngine::ExecuteStep(JobsRegistry::RunningJob& running, - size_t workerIndex) - { - assert(running.IsValid()); - - if (running.IsPauseScheduled()) - { - running.GetJob().Stop(JobStopReason_Paused); - running.MarkPause(); - return false; - } - - if (running.IsCancelScheduled()) - { - running.GetJob().Stop(JobStopReason_Canceled); - running.MarkCanceled(); - return false; - } - - JobStepResult result; - - try - { - result = running.GetJob().Step(running.GetId()); - } - catch (OrthancException& e) - { - result = JobStepResult::Failure(e); - } - catch (boost::bad_lexical_cast&) - { - result = JobStepResult::Failure(ErrorCode_BadFileFormat, NULL); - } - catch (...) - { - result = JobStepResult::Failure(ErrorCode_InternalError, NULL); - } - - switch (result.GetCode()) - { - case JobStepCode_Success: - running.GetJob().Stop(JobStopReason_Success); - running.UpdateStatus(ErrorCode_Success, ""); - running.MarkSuccess(); - return false; - - case JobStepCode_Failure: - running.GetJob().Stop(JobStopReason_Failure); - running.UpdateStatus(result.GetFailureCode(), result.GetFailureDetails()); - running.MarkFailure(); - return false; - - case JobStepCode_Retry: - running.GetJob().Stop(JobStopReason_Retry); - running.UpdateStatus(ErrorCode_Success, ""); - running.MarkRetry(result.GetRetryTimeout()); - return false; - - case JobStepCode_Continue: - running.UpdateStatus(ErrorCode_Success, ""); - return true; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - - - void JobsEngine::RetryHandler(JobsEngine* engine) - { - assert(engine != NULL); - - while (engine->IsRunning()) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(engine->threadSleep_)); - engine->GetRegistry().ScheduleRetries(); - } - } - - - void JobsEngine::Worker(JobsEngine* engine, - size_t workerIndex) - { - assert(engine != NULL); - - LOG(INFO) << "Worker thread " << workerIndex << " has started"; - - while (engine->IsRunning()) - { - JobsRegistry::RunningJob running(engine->GetRegistry(), engine->threadSleep_); - - if (running.IsValid()) - { - LOG(INFO) << "Executing job with priority " << running.GetPriority() - << " in worker thread " << workerIndex << ": " << running.GetId(); - - while (engine->IsRunning()) - { - if (!engine->ExecuteStep(running, workerIndex)) - { - break; - } - } - } - } - } - - - JobsEngine::JobsEngine(size_t maxCompletedJobs) : - state_(State_Setup), - registry_(new JobsRegistry(maxCompletedJobs)), - threadSleep_(200), - workers_(1) - { - } - - - JobsEngine::~JobsEngine() - { - if (state_ != State_Setup && - state_ != State_Done) - { - LOG(ERROR) << "INTERNAL ERROR: JobsEngine::Stop() should be invoked manually to avoid mess in the destruction order!"; - Stop(); - } - } - - - JobsRegistry& JobsEngine::GetRegistry() - { - if (registry_.get() == NULL) - { - throw OrthancException(ErrorCode_InternalError); - } - - return *registry_; - } - - - void JobsEngine::LoadRegistryFromJson(IJobUnserializer& unserializer, - const Json::Value& serialized) - { - boost::mutex::scoped_lock lock(stateMutex_); - - if (state_ != State_Setup) - { - // Can only be invoked before calling "Start()" - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - assert(registry_.get() != NULL); - const size_t maxCompletedJobs = registry_->GetMaxCompletedJobs(); - registry_.reset(new JobsRegistry(unserializer, serialized, maxCompletedJobs)); - } - - - void JobsEngine::LoadRegistryFromString(IJobUnserializer& unserializer, - const std::string& serialized) - { - Json::Value value; - Json::Reader reader; - if (reader.parse(serialized, value)) - { - LoadRegistryFromJson(unserializer, value); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - void JobsEngine::SetWorkersCount(size_t count) - { - boost::mutex::scoped_lock lock(stateMutex_); - - if (state_ != State_Setup) - { - // Can only be invoked before calling "Start()" - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - workers_.resize(count); - } - - - void JobsEngine::SetThreadSleep(unsigned int sleep) - { - boost::mutex::scoped_lock lock(stateMutex_); - - if (state_ != State_Setup) - { - // Can only be invoked before calling "Start()" - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - threadSleep_ = sleep; - } - - - void JobsEngine::Start() - { - boost::mutex::scoped_lock lock(stateMutex_); - - if (state_ != State_Setup) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - retryHandler_ = boost::thread(RetryHandler, this); - - if (workers_.size() == 0) - { - // Use all the available CPUs - size_t n = boost::thread::hardware_concurrency(); - - if (n == 0) - { - n = 1; - } - - workers_.resize(n); - } - - for (size_t i = 0; i < workers_.size(); i++) - { - assert(workers_[i] == NULL); - workers_[i] = new boost::thread(Worker, this, i); - } - - state_ = State_Running; - - LOG(WARNING) << "The jobs engine has started with " << workers_.size() << " threads"; - } - - - void JobsEngine::Stop() - { - { - boost::mutex::scoped_lock lock(stateMutex_); - - if (state_ != State_Running) - { - return; - } - - state_ = State_Stopping; - } - - LOG(INFO) << "Stopping the jobs engine"; - - if (retryHandler_.joinable()) - { - retryHandler_.join(); - } - - for (size_t i = 0; i < workers_.size(); i++) - { - assert(workers_[i] != NULL); - - if (workers_[i]->joinable()) - { - workers_[i]->join(); - } - - delete workers_[i]; - } - - { - boost::mutex::scoped_lock lock(stateMutex_); - state_ = State_Done; - } - - LOG(WARNING) << "The jobs engine has stopped"; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobsEngine.h --- a/Core/JobsEngine/JobsEngine.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "JobsRegistry.h" - -#include "../Compatibility.h" - -#include - -namespace Orthanc -{ - class JobsEngine : public boost::noncopyable - { - private: - enum State - { - State_Setup, - State_Running, - State_Stopping, - State_Done - }; - - boost::mutex stateMutex_; - State state_; - std::unique_ptr registry_; - boost::thread retryHandler_; - unsigned int threadSleep_; - std::vector workers_; - - bool IsRunning(); - - bool ExecuteStep(JobsRegistry::RunningJob& running, - size_t workerIndex); - - static void RetryHandler(JobsEngine* engine); - - static void Worker(JobsEngine* engine, - size_t workerIndex); - - public: - JobsEngine(size_t maxCompletedJobs); - - ~JobsEngine(); - - JobsRegistry& GetRegistry(); - - void LoadRegistryFromJson(IJobUnserializer& unserializer, - const Json::Value& serialized); - - void LoadRegistryFromString(IJobUnserializer& unserializer, - const std::string& serialized); - - void SetWorkersCount(size_t count); - - void SetThreadSleep(unsigned int sleep); - - void Start(); - - void Stop(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobsRegistry.cpp --- a/Core/JobsEngine/JobsRegistry.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1474 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "JobsRegistry.h" - -#include "../Logging.h" -#include "../OrthancException.h" -#include "../Toolbox.h" -#include "../SerializationToolbox.h" - -namespace Orthanc -{ - static const char* STATE = "State"; - static const char* TYPE = "Type"; - static const char* PRIORITY = "Priority"; - static const char* JOB = "Job"; - static const char* JOBS = "Jobs"; - static const char* JOBS_REGISTRY = "JobsRegistry"; - static const char* CREATION_TIME = "CreationTime"; - static const char* LAST_CHANGE_TIME = "LastChangeTime"; - static const char* RUNTIME = "Runtime"; - - - class JobsRegistry::JobHandler : public boost::noncopyable - { - private: - std::string id_; - JobState state_; - std::string jobType_; - std::unique_ptr job_; - int priority_; // "+inf()" means highest priority - boost::posix_time::ptime creationTime_; - boost::posix_time::ptime lastStateChangeTime_; - boost::posix_time::time_duration runtime_; - boost::posix_time::ptime retryTime_; - bool pauseScheduled_; - bool cancelScheduled_; - JobStatus lastStatus_; - - void Touch() - { - const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); - - if (state_ == JobState_Running) - { - runtime_ += (now - lastStateChangeTime_); - } - - lastStateChangeTime_ = now; - } - - void SetStateInternal(JobState state) - { - state_ = state; - pauseScheduled_ = false; - cancelScheduled_ = false; - Touch(); - } - - public: - JobHandler(IJob* job, - int priority) : - id_(Toolbox::GenerateUuid()), - state_(JobState_Pending), - job_(job), - priority_(priority), - creationTime_(boost::posix_time::microsec_clock::universal_time()), - lastStateChangeTime_(creationTime_), - runtime_(boost::posix_time::milliseconds(0)), - retryTime_(creationTime_), - pauseScheduled_(false), - cancelScheduled_(false) - { - if (job == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - - job->GetJobType(jobType_); - job->Start(); - - lastStatus_ = JobStatus(ErrorCode_Success, "", *job_); - } - - const std::string& GetId() const - { - return id_; - } - - IJob& GetJob() const - { - assert(job_.get() != NULL); - return *job_; - } - - void SetPriority(int priority) - { - priority_ = priority; - } - - int GetPriority() const - { - return priority_; - } - - JobState GetState() const - { - return state_; - } - - void SetState(JobState state) - { - if (state == JobState_Retry) - { - // Use "SetRetryState()" - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - SetStateInternal(state); - } - } - - void SetRetryState(unsigned int timeout) - { - if (state_ == JobState_Running) - { - SetStateInternal(JobState_Retry); - retryTime_ = (boost::posix_time::microsec_clock::universal_time() + - boost::posix_time::milliseconds(timeout)); - } - else - { - // Only valid for running jobs - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - void SchedulePause() - { - if (state_ == JobState_Running) - { - pauseScheduled_ = true; - } - else - { - // Only valid for running jobs - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - void ScheduleCancel() - { - if (state_ == JobState_Running) - { - cancelScheduled_ = true; - } - else - { - // Only valid for running jobs - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - bool IsPauseScheduled() - { - return pauseScheduled_; - } - - bool IsCancelScheduled() - { - return cancelScheduled_; - } - - bool IsRetryReady(const boost::posix_time::ptime& now) const - { - if (state_ != JobState_Retry) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return retryTime_ <= now; - } - } - - const boost::posix_time::ptime& GetCreationTime() const - { - return creationTime_; - } - - const boost::posix_time::ptime& GetLastStateChangeTime() const - { - return lastStateChangeTime_; - } - - void SetLastStateChangeTime(const boost::posix_time::ptime& time) - { - lastStateChangeTime_ = time; - } - - const boost::posix_time::time_duration& GetRuntime() const - { - return runtime_; - } - - const JobStatus& GetLastStatus() const - { - return lastStatus_; - } - - void SetLastStatus(const JobStatus& status) - { - lastStatus_ = status; - Touch(); - } - - void SetLastErrorCode(ErrorCode code) - { - lastStatus_.SetErrorCode(code); - } - - bool Serialize(Json::Value& target) const - { - target = Json::objectValue; - - bool ok; - - if (state_ == JobState_Running) - { - // WARNING: Cannot directly access the "job_" member, as long - // as a "RunningJob" instance is running. We do not use a - // mutex at the "JobHandler" level, as serialization would be - // blocked while a step in the job is running. Instead, we - // save a snapshot of the serialized job. (*) - - if (lastStatus_.HasSerialized()) - { - target[JOB] = lastStatus_.GetSerialized(); - ok = true; - } - else - { - ok = false; - } - } - else - { - ok = job_->Serialize(target[JOB]); - } - - if (ok) - { - target[STATE] = EnumerationToString(state_); - target[PRIORITY] = priority_; - target[CREATION_TIME] = boost::posix_time::to_iso_string(creationTime_); - target[LAST_CHANGE_TIME] = boost::posix_time::to_iso_string(lastStateChangeTime_); - target[RUNTIME] = static_cast(runtime_.total_milliseconds()); - return true; - } - else - { - VLOG(1) << "Job backup is not supported for job of type: " << jobType_; - return false; - } - } - - JobHandler(IJobUnserializer& unserializer, - const Json::Value& serialized, - const std::string& id) : - id_(id), - pauseScheduled_(false), - cancelScheduled_(false) - { - state_ = StringToJobState(SerializationToolbox::ReadString(serialized, STATE)); - priority_ = SerializationToolbox::ReadInteger(serialized, PRIORITY); - creationTime_ = boost::posix_time::from_iso_string - (SerializationToolbox::ReadString(serialized, CREATION_TIME)); - lastStateChangeTime_ = boost::posix_time::from_iso_string - (SerializationToolbox::ReadString(serialized, LAST_CHANGE_TIME)); - runtime_ = boost::posix_time::milliseconds - (SerializationToolbox::ReadInteger(serialized, RUNTIME)); - - retryTime_ = creationTime_; - - job_.reset(unserializer.UnserializeJob(serialized[JOB])); - job_->GetJobType(jobType_); - job_->Start(); - - lastStatus_ = JobStatus(ErrorCode_Success, "", *job_); - } - }; - - - bool JobsRegistry::PriorityComparator::operator() (JobHandler*& a, - JobHandler*& b) const - { - return a->GetPriority() < b->GetPriority(); - } - - -#if defined(NDEBUG) - void JobsRegistry::CheckInvariants() const - { - } - -#else - bool JobsRegistry::IsPendingJob(const JobHandler& job) const - { - PendingJobs copy = pendingJobs_; - while (!copy.empty()) - { - if (copy.top() == &job) - { - return true; - } - - copy.pop(); - } - - return false; - } - - bool JobsRegistry::IsCompletedJob(JobHandler& job) const - { - for (CompletedJobs::const_iterator it = completedJobs_.begin(); - it != completedJobs_.end(); ++it) - { - if (*it == &job) - { - return true; - } - } - - return false; - } - - bool JobsRegistry::IsRetryJob(JobHandler& job) const - { - return retryJobs_.find(&job) != retryJobs_.end(); - } - - void JobsRegistry::CheckInvariants() const - { - { - PendingJobs copy = pendingJobs_; - while (!copy.empty()) - { - assert(copy.top()->GetState() == JobState_Pending); - copy.pop(); - } - } - - assert(completedJobs_.size() <= maxCompletedJobs_); - - for (CompletedJobs::const_iterator it = completedJobs_.begin(); - it != completedJobs_.end(); ++it) - { - assert((*it)->GetState() == JobState_Success || - (*it)->GetState() == JobState_Failure); - } - - for (RetryJobs::const_iterator it = retryJobs_.begin(); - it != retryJobs_.end(); ++it) - { - assert((*it)->GetState() == JobState_Retry); - } - - for (JobsIndex::const_iterator it = jobsIndex_.begin(); - it != jobsIndex_.end(); ++it) - { - JobHandler& job = *it->second; - - assert(job.GetId() == it->first); - - switch (job.GetState()) - { - case JobState_Pending: - assert(!IsRetryJob(job) && IsPendingJob(job) && !IsCompletedJob(job)); - break; - - case JobState_Success: - case JobState_Failure: - assert(!IsRetryJob(job) && !IsPendingJob(job) && IsCompletedJob(job)); - break; - - case JobState_Retry: - assert(IsRetryJob(job) && !IsPendingJob(job) && !IsCompletedJob(job)); - break; - - case JobState_Running: - case JobState_Paused: - assert(!IsRetryJob(job) && !IsPendingJob(job) && !IsCompletedJob(job)); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - } -#endif - - - void JobsRegistry::ForgetOldCompletedJobs() - { - while (completedJobs_.size() > maxCompletedJobs_) - { - assert(completedJobs_.front() != NULL); - - std::string id = completedJobs_.front()->GetId(); - assert(jobsIndex_.find(id) != jobsIndex_.end()); - - jobsIndex_.erase(id); - delete(completedJobs_.front()); - completedJobs_.pop_front(); - } - - CheckInvariants(); - } - - - void JobsRegistry::SetCompletedJob(JobHandler& job, - bool success) - { - job.SetState(success ? JobState_Success : JobState_Failure); - - completedJobs_.push_back(&job); - someJobComplete_.notify_all(); - } - - - void JobsRegistry::MarkRunningAsCompleted(JobHandler& job, - CompletedReason reason) - { - const char* tmp; - - switch (reason) - { - case CompletedReason_Success: - tmp = "success"; - break; - - case CompletedReason_Failure: - tmp = "failure"; - break; - - case CompletedReason_Canceled: - tmp = "cancel"; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - LOG(INFO) << "Job has completed with " << tmp << ": " << job.GetId(); - - CheckInvariants(); - - assert(job.GetState() == JobState_Running); - SetCompletedJob(job, reason == CompletedReason_Success); - - if (reason == CompletedReason_Canceled) - { - job.SetLastErrorCode(ErrorCode_CanceledJob); - } - - if (observer_ != NULL) - { - if (reason == CompletedReason_Success) - { - observer_->SignalJobSuccess(job.GetId()); - } - else - { - observer_->SignalJobFailure(job.GetId()); - } - } - - // WARNING: The following call might make "job" invalid if the job - // history size is empty - ForgetOldCompletedJobs(); - } - - - void JobsRegistry::MarkRunningAsRetry(JobHandler& job, - unsigned int timeout) - { - LOG(INFO) << "Job scheduled for retry in " << timeout << "ms: " << job.GetId(); - - CheckInvariants(); - - assert(job.GetState() == JobState_Running && - retryJobs_.find(&job) == retryJobs_.end()); - - retryJobs_.insert(&job); - job.SetRetryState(timeout); - - CheckInvariants(); - } - - - void JobsRegistry::MarkRunningAsPaused(JobHandler& job) - { - LOG(INFO) << "Job paused: " << job.GetId(); - - CheckInvariants(); - assert(job.GetState() == JobState_Running); - - job.SetState(JobState_Paused); - - CheckInvariants(); - } - - - bool JobsRegistry::GetStateInternal(JobState& state, - const std::string& id) - { - CheckInvariants(); - - JobsIndex::const_iterator it = jobsIndex_.find(id); - if (it == jobsIndex_.end()) - { - return false; - } - else - { - state = it->second->GetState(); - return true; - } - } - - - JobsRegistry::~JobsRegistry() - { - for (JobsIndex::iterator it = jobsIndex_.begin(); it != jobsIndex_.end(); ++it) - { - assert(it->second != NULL); - delete it->second; - } - } - - - void JobsRegistry::SetMaxCompletedJobs(size_t n) - { - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - LOG(INFO) << "The size of the history of the jobs engine is set to: " << n << " job(s)"; - - maxCompletedJobs_ = n; - ForgetOldCompletedJobs(); - } - - - size_t JobsRegistry::GetMaxCompletedJobs() - { - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - return maxCompletedJobs_; - } - - - void JobsRegistry::ListJobs(std::set& target) - { - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - for (JobsIndex::const_iterator it = jobsIndex_.begin(); - it != jobsIndex_.end(); ++it) - { - target.insert(it->first); - } - } - - - bool JobsRegistry::GetJobInfo(JobInfo& target, - const std::string& id) - { - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - JobsIndex::const_iterator found = jobsIndex_.find(id); - - if (found == jobsIndex_.end()) - { - return false; - } - else - { - const JobHandler& handler = *found->second; - target = JobInfo(handler.GetId(), - handler.GetPriority(), - handler.GetState(), - handler.GetLastStatus(), - handler.GetCreationTime(), - handler.GetLastStateChangeTime(), - handler.GetRuntime()); - return true; - } - } - - - bool JobsRegistry::GetJobOutput(std::string& output, - MimeType& mime, - const std::string& job, - const std::string& key) - { - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - JobsIndex::const_iterator found = jobsIndex_.find(job); - - if (found == jobsIndex_.end()) - { - return false; - } - else - { - const JobHandler& handler = *found->second; - - if (handler.GetState() == JobState_Success) - { - return handler.GetJob().GetOutput(output, mime, key); - } - else - { - return false; - } - } - } - - - void JobsRegistry::SubmitInternal(std::string& id, - JobHandler* handler) - { - if (handler == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - - std::unique_ptr protection(handler); - - { - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - id = handler->GetId(); - int priority = handler->GetPriority(); - - jobsIndex_.insert(std::make_pair(id, protection.release())); - - switch (handler->GetState()) - { - case JobState_Pending: - case JobState_Retry: - case JobState_Running: - handler->SetState(JobState_Pending); - pendingJobs_.push(handler); - pendingJobAvailable_.notify_one(); - break; - - case JobState_Success: - SetCompletedJob(*handler, true); - break; - - case JobState_Failure: - SetCompletedJob(*handler, false); - break; - - case JobState_Paused: - break; - - default: - { - std::string details = ("A job should not be loaded from state: " + - std::string(EnumerationToString(handler->GetState()))); - throw OrthancException(ErrorCode_InternalError, details); - } - } - - LOG(INFO) << "New job submitted with priority " << priority << ": " << id; - - if (observer_ != NULL) - { - observer_->SignalJobSubmitted(id); - } - - // WARNING: The following call might make "handler" invalid if - // the job history size is empty - ForgetOldCompletedJobs(); - } - } - - - void JobsRegistry::Submit(std::string& id, - IJob* job, // Takes ownership - int priority) - { - SubmitInternal(id, new JobHandler(job, priority)); - } - - - void JobsRegistry::Submit(IJob* job, // Takes ownership - int priority) - { - std::string id; - SubmitInternal(id, new JobHandler(job, priority)); - } - - - void JobsRegistry::SubmitAndWait(Json::Value& successContent, - IJob* job, // Takes ownership - int priority) - { - std::string id; - Submit(id, job, priority); - - JobState state = JobState_Pending; // Dummy initialization - - { - boost::mutex::scoped_lock lock(mutex_); - - for (;;) - { - if (!GetStateInternal(state, id)) - { - // Job has finished and has been lost (typically happens if - // "JobsHistorySize" is 0) - throw OrthancException(ErrorCode_InexistentItem, - "Cannot retrieve the status of the job, " - "make sure that \"JobsHistorySize\" is not 0"); - } - else if (state == JobState_Failure) - { - // Failure - JobsIndex::const_iterator it = jobsIndex_.find(id); - if (it != jobsIndex_.end()) // Should always be true, already tested in GetStateInternal() - { - ErrorCode code = it->second->GetLastStatus().GetErrorCode(); - const std::string& details = it->second->GetLastStatus().GetDetails(); - - if (details.empty()) - { - throw OrthancException(code); - } - else - { - throw OrthancException(code, details); - } - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - } - else if (state == JobState_Success) - { - // Success, try and retrieve the status of the job - JobsIndex::const_iterator it = jobsIndex_.find(id); - if (it == jobsIndex_.end()) - { - // Should not happen - state = JobState_Failure; - } - else - { - const JobStatus& status = it->second->GetLastStatus(); - successContent = status.GetPublicContent(); - } - - return; - } - else - { - // This job has not finished yet, wait for new completion - someJobComplete_.wait(lock); - } - } - } - } - - - bool JobsRegistry::SetPriority(const std::string& id, - int priority) - { - LOG(INFO) << "Changing priority to " << priority << " for job: " << id; - - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - JobsIndex::iterator found = jobsIndex_.find(id); - - if (found == jobsIndex_.end()) - { - LOG(WARNING) << "Unknown job: " << id; - return false; - } - else - { - found->second->SetPriority(priority); - - if (found->second->GetState() == JobState_Pending) - { - // If the job is pending, we need to reconstruct the - // priority queue, as the heap condition has changed - - PendingJobs copy; - std::swap(copy, pendingJobs_); - - assert(pendingJobs_.empty()); - while (!copy.empty()) - { - pendingJobs_.push(copy.top()); - copy.pop(); - } - } - - CheckInvariants(); - return true; - } - } - - - void JobsRegistry::RemovePendingJob(const std::string& id) - { - // If the job is pending, we need to reconstruct the priority - // queue to remove it - PendingJobs copy; - std::swap(copy, pendingJobs_); - - assert(pendingJobs_.empty()); - while (!copy.empty()) - { - if (copy.top()->GetId() != id) - { - pendingJobs_.push(copy.top()); - } - - copy.pop(); - } - } - - - void JobsRegistry::RemoveRetryJob(JobHandler* handler) - { - RetryJobs::iterator item = retryJobs_.find(handler); - assert(item != retryJobs_.end()); - retryJobs_.erase(item); - } - - - bool JobsRegistry::Pause(const std::string& id) - { - LOG(INFO) << "Pausing job: " << id; - - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - JobsIndex::iterator found = jobsIndex_.find(id); - - if (found == jobsIndex_.end()) - { - LOG(WARNING) << "Unknown job: " << id; - return false; - } - else - { - switch (found->second->GetState()) - { - case JobState_Pending: - RemovePendingJob(id); - found->second->SetState(JobState_Paused); - break; - - case JobState_Retry: - RemoveRetryJob(found->second); - found->second->SetState(JobState_Paused); - break; - - case JobState_Paused: - case JobState_Success: - case JobState_Failure: - // Nothing to be done - break; - - case JobState_Running: - found->second->SchedulePause(); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - CheckInvariants(); - return true; - } - } - - - bool JobsRegistry::Cancel(const std::string& id) - { - LOG(INFO) << "Canceling job: " << id; - - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - JobsIndex::iterator found = jobsIndex_.find(id); - - if (found == jobsIndex_.end()) - { - LOG(WARNING) << "Unknown job: " << id; - return false; - } - else - { - switch (found->second->GetState()) - { - case JobState_Pending: - RemovePendingJob(id); - SetCompletedJob(*found->second, false); - found->second->SetLastErrorCode(ErrorCode_CanceledJob); - break; - - case JobState_Retry: - RemoveRetryJob(found->second); - SetCompletedJob(*found->second, false); - found->second->SetLastErrorCode(ErrorCode_CanceledJob); - break; - - case JobState_Paused: - SetCompletedJob(*found->second, false); - found->second->SetLastErrorCode(ErrorCode_CanceledJob); - break; - - case JobState_Success: - case JobState_Failure: - // Nothing to be done - break; - - case JobState_Running: - found->second->ScheduleCancel(); - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - // WARNING: The following call might make "handler" invalid if - // the job history size is empty - ForgetOldCompletedJobs(); - - return true; - } - } - - - bool JobsRegistry::Resume(const std::string& id) - { - LOG(INFO) << "Resuming job: " << id; - - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - JobsIndex::iterator found = jobsIndex_.find(id); - - if (found == jobsIndex_.end()) - { - LOG(WARNING) << "Unknown job: " << id; - return false; - } - else if (found->second->GetState() != JobState_Paused) - { - LOG(WARNING) << "Cannot resume a job that is not paused: " << id; - return false; - } - else - { - found->second->SetState(JobState_Pending); - pendingJobs_.push(found->second); - pendingJobAvailable_.notify_one(); - CheckInvariants(); - return true; - } - } - - - bool JobsRegistry::Resubmit(const std::string& id) - { - LOG(INFO) << "Resubmitting failed job: " << id; - - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - JobsIndex::iterator found = jobsIndex_.find(id); - - if (found == jobsIndex_.end()) - { - LOG(WARNING) << "Unknown job: " << id; - return false; - } - else if (found->second->GetState() != JobState_Failure) - { - LOG(WARNING) << "Cannot resubmit a job that has not failed: " << id; - return false; - } - else - { - found->second->GetJob().Reset(); - - bool ok = false; - for (CompletedJobs::iterator it = completedJobs_.begin(); - it != completedJobs_.end(); ++it) - { - if (*it == found->second) - { - ok = true; - completedJobs_.erase(it); - break; - } - } - - assert(ok); - - found->second->SetState(JobState_Pending); - pendingJobs_.push(found->second); - pendingJobAvailable_.notify_one(); - - CheckInvariants(); - return true; - } - } - - - void JobsRegistry::ScheduleRetries() - { - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - RetryJobs copy; - std::swap(copy, retryJobs_); - - const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); - - assert(retryJobs_.empty()); - for (RetryJobs::iterator it = copy.begin(); it != copy.end(); ++it) - { - if ((*it)->IsRetryReady(now)) - { - LOG(INFO) << "Retrying job: " << (*it)->GetId(); - (*it)->SetState(JobState_Pending); - pendingJobs_.push(*it); - pendingJobAvailable_.notify_one(); - } - else - { - retryJobs_.insert(*it); - } - } - - CheckInvariants(); - } - - - bool JobsRegistry::GetState(JobState& state, - const std::string& id) - { - boost::mutex::scoped_lock lock(mutex_); - return GetStateInternal(state, id); - } - - - void JobsRegistry::SetObserver(JobsRegistry::IObserver& observer) - { - boost::mutex::scoped_lock lock(mutex_); - observer_ = &observer; - } - - - void JobsRegistry::ResetObserver() - { - boost::mutex::scoped_lock lock(mutex_); - observer_ = NULL; - } - - - JobsRegistry::RunningJob::RunningJob(JobsRegistry& registry, - unsigned int timeout) : - registry_(registry), - handler_(NULL), - targetState_(JobState_Failure), - targetRetryTimeout_(0), - canceled_(false) - { - { - boost::mutex::scoped_lock lock(registry_.mutex_); - - while (registry_.pendingJobs_.empty()) - { - if (timeout == 0) - { - registry_.pendingJobAvailable_.wait(lock); - } - else - { - bool success = registry_.pendingJobAvailable_.timed_wait - (lock, boost::posix_time::milliseconds(timeout)); - if (!success) - { - // No pending job - return; - } - } - } - - handler_ = registry_.pendingJobs_.top(); - registry_.pendingJobs_.pop(); - - assert(handler_->GetState() == JobState_Pending); - handler_->SetState(JobState_Running); - handler_->SetLastErrorCode(ErrorCode_Success); - - job_ = &handler_->GetJob(); - id_ = handler_->GetId(); - priority_ = handler_->GetPriority(); - } - } - - - JobsRegistry::RunningJob::~RunningJob() - { - if (IsValid()) - { - boost::mutex::scoped_lock lock(registry_.mutex_); - - switch (targetState_) - { - case JobState_Failure: - registry_.MarkRunningAsCompleted - (*handler_, canceled_ ? CompletedReason_Canceled : CompletedReason_Failure); - break; - - case JobState_Success: - registry_.MarkRunningAsCompleted(*handler_, CompletedReason_Success); - break; - - case JobState_Paused: - registry_.MarkRunningAsPaused(*handler_); - break; - - case JobState_Retry: - registry_.MarkRunningAsRetry(*handler_, targetRetryTimeout_); - break; - - default: - assert(0); - } - } - } - - - bool JobsRegistry::RunningJob::IsValid() const - { - return (handler_ != NULL && - job_ != NULL); - } - - - const std::string& JobsRegistry::RunningJob::GetId() const - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return id_; - } - } - - - int JobsRegistry::RunningJob::GetPriority() const - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return priority_; - } - } - - - IJob& JobsRegistry::RunningJob::GetJob() - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - return *job_; - } - } - - - bool JobsRegistry::RunningJob::IsPauseScheduled() - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - boost::mutex::scoped_lock lock(registry_.mutex_); - registry_.CheckInvariants(); - assert(handler_->GetState() == JobState_Running); - - return handler_->IsPauseScheduled(); - } - } - - - bool JobsRegistry::RunningJob::IsCancelScheduled() - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - boost::mutex::scoped_lock lock(registry_.mutex_); - registry_.CheckInvariants(); - assert(handler_->GetState() == JobState_Running); - - return handler_->IsCancelScheduled(); - } - } - - - void JobsRegistry::RunningJob::MarkSuccess() - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - targetState_ = JobState_Success; - } - } - - - void JobsRegistry::RunningJob::MarkFailure() - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - targetState_ = JobState_Failure; - } - } - - - void JobsRegistry::RunningJob::MarkCanceled() - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - targetState_ = JobState_Failure; - canceled_ = true; - } - } - - - void JobsRegistry::RunningJob::MarkPause() - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - targetState_ = JobState_Paused; - } - } - - - void JobsRegistry::RunningJob::MarkRetry(unsigned int timeout) - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - targetState_ = JobState_Retry; - targetRetryTimeout_ = timeout; - } - } - - - void JobsRegistry::RunningJob::UpdateStatus(ErrorCode code, - const std::string& details) - { - if (!IsValid()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - JobStatus status(code, details, *job_); - - boost::mutex::scoped_lock lock(registry_.mutex_); - registry_.CheckInvariants(); - assert(handler_->GetState() == JobState_Running); - - handler_->SetLastStatus(status); - } - } - - - - void JobsRegistry::Serialize(Json::Value& target) - { - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - target = Json::objectValue; - target[TYPE] = JOBS_REGISTRY; - target[JOBS] = Json::objectValue; - - for (JobsIndex::const_iterator it = jobsIndex_.begin(); - it != jobsIndex_.end(); ++it) - { - Json::Value v; - if (it->second->Serialize(v)) - { - target[JOBS][it->first] = v; - } - } - } - - - JobsRegistry::JobsRegistry(IJobUnserializer& unserializer, - const Json::Value& s, - size_t maxCompletedJobs) : - maxCompletedJobs_(maxCompletedJobs), - observer_(NULL) - { - if (SerializationToolbox::ReadString(s, TYPE) != JOBS_REGISTRY || - !s.isMember(JOBS) || - s[JOBS].type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value::Members members = s[JOBS].getMemberNames(); - - for (Json::Value::Members::const_iterator it = members.begin(); - it != members.end(); ++it) - { - std::unique_ptr job; - - try - { - job.reset(new JobHandler(unserializer, s[JOBS][*it], *it)); - } - catch (OrthancException& e) - { - LOG(WARNING) << "Cannot unserialize one job from previous execution, " - << "skipping it: " << e.What(); - continue; - } - - const boost::posix_time::ptime lastChangeTime = job->GetLastStateChangeTime(); - - std::string id; - SubmitInternal(id, job.release()); - - // Check whether the job has not been removed (which could be - // the case if the "maxCompletedJobs_" value gets smaller) - JobsIndex::iterator found = jobsIndex_.find(id); - if (found != jobsIndex_.end()) - { - // The job still lies in the history: Update the time of its - // last change to the time that was serialized - assert(found->second != NULL); - found->second->SetLastStateChangeTime(lastChangeTime); - } - } - } - - - void JobsRegistry::GetStatistics(unsigned int& pending, - unsigned int& running, - unsigned int& success, - unsigned int& failed) - { - boost::mutex::scoped_lock lock(mutex_); - CheckInvariants(); - - pending = 0; - running = 0; - success = 0; - failed = 0; - - for (JobsIndex::const_iterator it = jobsIndex_.begin(); - it != jobsIndex_.end(); ++it) - { - JobHandler& job = *it->second; - - switch (job.GetState()) - { - case JobState_Retry: - case JobState_Pending: - pending ++; - break; - - case JobState_Paused: - case JobState_Running: - running ++; - break; - - case JobState_Success: - success ++; - break; - - case JobState_Failure: - failed ++; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/JobsRegistry.h --- a/Core/JobsEngine/JobsRegistry.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,255 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The job engine cannot be used in sandboxed environments -#endif - -#include "JobInfo.h" -#include "IJobUnserializer.h" - -#include -#include -#include -#include -#include - -namespace Orthanc -{ - // This class handles the state machine of the jobs engine - class JobsRegistry : public boost::noncopyable - { - public: - class IObserver : public boost::noncopyable - { - public: - virtual ~IObserver() - { - } - - virtual void SignalJobSubmitted(const std::string& jobId) = 0; - - virtual void SignalJobSuccess(const std::string& jobId) = 0; - - virtual void SignalJobFailure(const std::string& jobId) = 0; - }; - - private: - enum CompletedReason - { - CompletedReason_Success, - CompletedReason_Failure, - CompletedReason_Canceled - }; - - class JobHandler; - - struct PriorityComparator - { - bool operator() (JobHandler*& a, - JobHandler*& b) const; - }; - - typedef std::map JobsIndex; - typedef std::list CompletedJobs; - typedef std::set RetryJobs; - typedef std::priority_queue, // Could be a "std::deque" - PriorityComparator> PendingJobs; - - boost::mutex mutex_; - JobsIndex jobsIndex_; - PendingJobs pendingJobs_; - CompletedJobs completedJobs_; - RetryJobs retryJobs_; - - boost::condition_variable pendingJobAvailable_; - boost::condition_variable someJobComplete_; - size_t maxCompletedJobs_; - - IObserver* observer_; - - -#ifndef NDEBUG - bool IsPendingJob(const JobHandler& job) const; - - bool IsCompletedJob(JobHandler& job) const; - - bool IsRetryJob(JobHandler& job) const; -#endif - - void CheckInvariants() const; - - void ForgetOldCompletedJobs(); - - void SetCompletedJob(JobHandler& job, - bool success); - - void MarkRunningAsCompleted(JobHandler& job, - CompletedReason reason); - - void MarkRunningAsRetry(JobHandler& job, - unsigned int timeout); - - void MarkRunningAsPaused(JobHandler& job); - - bool GetStateInternal(JobState& state, - const std::string& id); - - void RemovePendingJob(const std::string& id); - - void RemoveRetryJob(JobHandler* handler); - - void SubmitInternal(std::string& id, - JobHandler* handler); - - public: - JobsRegistry(size_t maxCompletedJobs) : - maxCompletedJobs_(maxCompletedJobs), - observer_(NULL) - { - } - - JobsRegistry(IJobUnserializer& unserializer, - const Json::Value& s, - size_t maxCompletedJobs); - - ~JobsRegistry(); - - void SetMaxCompletedJobs(size_t i); - - size_t GetMaxCompletedJobs(); - - void ListJobs(std::set& target); - - bool GetJobInfo(JobInfo& target, - const std::string& id); - - bool GetJobOutput(std::string& output, - MimeType& mime, - const std::string& job, - const std::string& key); - - void Serialize(Json::Value& target); - - void Submit(std::string& id, - IJob* job, // Takes ownership - int priority); - - void Submit(IJob* job, // Takes ownership - int priority); - - void SubmitAndWait(Json::Value& successContent, - IJob* job, // Takes ownership - int priority); - - bool SetPriority(const std::string& id, - int priority); - - bool Pause(const std::string& id); - - bool Resume(const std::string& id); - - bool Resubmit(const std::string& id); - - bool Cancel(const std::string& id); - - void ScheduleRetries(); - - bool GetState(JobState& state, - const std::string& id); - - void SetObserver(IObserver& observer); - - void ResetObserver(); - - void GetStatistics(unsigned int& pending, - unsigned int& running, - unsigned int& success, - unsigned int& errors); - - class RunningJob : public boost::noncopyable - { - private: - JobsRegistry& registry_; - JobHandler* handler_; // Can only be accessed if the - // registry mutex is locked! - IJob* job_; // Will by design be in mutual exclusion, - // because only one RunningJob can be - // executed at a time on a JobHandler - - std::string id_; - int priority_; - JobState targetState_; - unsigned int targetRetryTimeout_; - bool canceled_; - - public: - RunningJob(JobsRegistry& registry, - unsigned int timeout); - - ~RunningJob(); - - bool IsValid() const; - - const std::string& GetId() const; - - int GetPriority() const; - - IJob& GetJob(); - - bool IsPauseScheduled(); - - bool IsCancelScheduled(); - - void MarkSuccess(); - - void MarkFailure(); - - void MarkPause(); - - void MarkCanceled(); - - void MarkRetry(unsigned int timeout); - - void UpdateStatus(ErrorCode code, - const std::string& details); - }; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/IJobOperation.h --- a/Core/JobsEngine/Operations/IJobOperation.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "JobOperationValues.h" - -namespace Orthanc -{ - class IJobOperation : public boost::noncopyable - { - public: - virtual ~IJobOperation() - { - } - - virtual void Apply(JobOperationValues& outputs, - const JobOperationValue& input) = 0; - - virtual void Serialize(Json::Value& result) const = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/JobOperationValue.h --- a/Core/JobsEngine/Operations/JobOperationValue.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include -#include - -namespace Orthanc -{ - class JobOperationValue : public boost::noncopyable - { - public: - enum Type - { - Type_DicomInstance, - Type_Null, - Type_String - }; - - private: - Type type_; - - protected: - JobOperationValue(Type type) : - type_(type) - { - } - - public: - virtual ~JobOperationValue() - { - } - - Type GetType() const - { - return type_; - } - - virtual JobOperationValue* Clone() const = 0; - - virtual void Serialize(Json::Value& target) const = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/JobOperationValues.cpp --- a/Core/JobsEngine/Operations/JobOperationValues.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../../PrecompiledHeaders.h" -#include "JobOperationValues.h" - -#include "../IJobUnserializer.h" -#include "../../OrthancException.h" - -#include -#include - -namespace Orthanc -{ - void JobOperationValues::Append(JobOperationValues& target, - bool clear) - { - target.Reserve(target.GetSize() + GetSize()); - - for (size_t i = 0; i < values_.size(); i++) - { - if (clear) - { - target.Append(values_[i]); - values_[i] = NULL; - } - else - { - target.Append(GetValue(i).Clone()); - } - } - - if (clear) - { - Clear(); - } - } - - - void JobOperationValues::Clear() - { - for (size_t i = 0; i < values_.size(); i++) - { - if (values_[i] != NULL) - { - delete values_[i]; - } - } - - values_.clear(); - } - - - void JobOperationValues::Append(JobOperationValue* value) // Takes ownership - { - if (value == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - else - { - values_.push_back(value); - } - } - - - JobOperationValue& JobOperationValues::GetValue(size_t index) const - { - if (index >= values_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - assert(values_[index] != NULL); - return *values_[index]; - } - } - - - void JobOperationValues::Serialize(Json::Value& target) const - { - target = Json::arrayValue; - - for (size_t i = 0; i < values_.size(); i++) - { - Json::Value tmp; - values_[i]->Serialize(tmp); - target.append(tmp); - } - } - - - JobOperationValues* JobOperationValues::Unserialize(IJobUnserializer& unserializer, - const Json::Value& source) - { - if (source.type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - std::unique_ptr result(new JobOperationValues); - - result->Reserve(source.size()); - - for (Json::Value::ArrayIndex i = 0; i < source.size(); i++) - { - result->Append(unserializer.UnserializeValue(source[i])); - } - - return result.release(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/JobOperationValues.h --- a/Core/JobsEngine/Operations/JobOperationValues.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "JobOperationValue.h" - -#include - -namespace Orthanc -{ - class IJobUnserializer; - - class JobOperationValues : public boost::noncopyable - { - private: - std::vector values_; - - void Append(JobOperationValues& target, - bool clear); - - public: - ~JobOperationValues() - { - Clear(); - } - - void Move(JobOperationValues& target) - { - return Append(target, true); - } - - void Copy(JobOperationValues& target) - { - return Append(target, false); - } - - void Clear(); - - void Reserve(size_t count) - { - values_.reserve(count); - } - - void Append(JobOperationValue* value); // Takes ownership - - size_t GetSize() const - { - return values_.size(); - } - - JobOperationValue& GetValue(size_t index) const; - - void Serialize(Json::Value& target) const; - - static JobOperationValues* Unserialize(IJobUnserializer& unserializer, - const Json::Value& source); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/LogJobOperation.cpp --- a/Core/JobsEngine/Operations/LogJobOperation.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../../PrecompiledHeaders.h" -#include "LogJobOperation.h" - -#include "../../Logging.h" -#include "StringOperationValue.h" - -namespace Orthanc -{ - void LogJobOperation::Apply(JobOperationValues& outputs, - const JobOperationValue& input) - { - switch (input.GetType()) - { - case JobOperationValue::Type_String: - LOG(INFO) << "Job value: " - << dynamic_cast(input).GetContent(); - break; - - case JobOperationValue::Type_Null: - LOG(INFO) << "Job value: (null)"; - break; - - default: - LOG(INFO) << "Job value: (unsupport)"; - break; - } - - outputs.Append(input.Clone()); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/LogJobOperation.h --- a/Core/JobsEngine/Operations/LogJobOperation.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IJobOperation.h" - -namespace Orthanc -{ - class LogJobOperation : public IJobOperation - { - public: - virtual void Apply(JobOperationValues& outputs, - const JobOperationValue& input); - - virtual void Serialize(Json::Value& result) const - { - result = Json::objectValue; - result["Type"] = "Log"; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/NullOperationValue.h --- a/Core/JobsEngine/Operations/NullOperationValue.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "JobOperationValue.h" - -namespace Orthanc -{ - class NullOperationValue : public JobOperationValue - { - public: - NullOperationValue() : - JobOperationValue(Type_Null) - { - } - - virtual JobOperationValue* Clone() const - { - return new NullOperationValue; - } - - virtual void Serialize(Json::Value& target) const - { - target = Json::objectValue; - target["Type"] = "Null"; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp --- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,475 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../../PrecompiledHeaders.h" -#include "SequenceOfOperationsJob.h" - -#include "../../Logging.h" -#include "../../OrthancException.h" -#include "../../SerializationToolbox.h" -#include "../IJobUnserializer.h" - -namespace Orthanc -{ - static const char* CURRENT = "Current"; - static const char* DESCRIPTION = "Description"; - static const char* NEXT_OPERATIONS = "Next"; - static const char* OPERATION = "Operation"; - static const char* OPERATIONS = "Operations"; - static const char* ORIGINAL_INPUTS = "OriginalInputs"; - static const char* TRAILING_TIMEOUT = "TrailingTimeout"; - static const char* TYPE = "Type"; - static const char* WORK_INPUTS = "WorkInputs"; - - - class SequenceOfOperationsJob::Operation : public boost::noncopyable - { - private: - size_t index_; - std::unique_ptr operation_; - std::unique_ptr originalInputs_; - std::unique_ptr workInputs_; - std::list nextOperations_; - size_t currentInput_; - - public: - Operation(size_t index, - IJobOperation* operation) : - index_(index), - operation_(operation), - originalInputs_(new JobOperationValues), - workInputs_(new JobOperationValues), - currentInput_(0) - { - if (operation == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - } - - void AddOriginalInput(const JobOperationValue& value) - { - if (currentInput_ != 0) - { - // Cannot add input after processing has started - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - originalInputs_->Append(value.Clone()); - } - } - - const JobOperationValues& GetOriginalInputs() const - { - return *originalInputs_; - } - - void Reset() - { - workInputs_->Clear(); - currentInput_ = 0; - } - - void AddNextOperation(Operation& other, - bool unserializing) - { - if (other.index_ <= index_) - { - throw OrthancException(ErrorCode_InternalError); - } - - if (!unserializing && - currentInput_ != 0) - { - // Cannot add input after processing has started - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - nextOperations_.push_back(&other); - } - } - - bool IsDone() const - { - return currentInput_ >= originalInputs_->GetSize() + workInputs_->GetSize(); - } - - void Step() - { - if (IsDone()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - const JobOperationValue* input; - - if (currentInput_ < originalInputs_->GetSize()) - { - input = &originalInputs_->GetValue(currentInput_); - } - else - { - input = &workInputs_->GetValue(currentInput_ - originalInputs_->GetSize()); - } - - JobOperationValues outputs; - operation_->Apply(outputs, *input); - - if (!nextOperations_.empty()) - { - std::list::iterator first = nextOperations_.begin(); - outputs.Move(*(*first)->workInputs_); - - std::list::iterator current = first; - ++current; - - while (current != nextOperations_.end()) - { - (*first)->workInputs_->Copy(*(*current)->workInputs_); - ++current; - } - } - - currentInput_ += 1; - } - - void Serialize(Json::Value& target) const - { - target = Json::objectValue; - target[CURRENT] = static_cast(currentInput_); - operation_->Serialize(target[OPERATION]); - originalInputs_->Serialize(target[ORIGINAL_INPUTS]); - workInputs_->Serialize(target[WORK_INPUTS]); - - Json::Value tmp = Json::arrayValue; - for (std::list::const_iterator it = nextOperations_.begin(); - it != nextOperations_.end(); ++it) - { - tmp.append(static_cast((*it)->index_)); - } - - target[NEXT_OPERATIONS] = tmp; - } - - Operation(IJobUnserializer& unserializer, - Json::Value::ArrayIndex index, - const Json::Value& serialized) : - index_(index) - { - if (serialized.type() != Json::objectValue || - !serialized.isMember(OPERATION) || - !serialized.isMember(ORIGINAL_INPUTS) || - !serialized.isMember(WORK_INPUTS)) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - currentInput_ = SerializationToolbox::ReadUnsignedInteger(serialized, CURRENT); - operation_.reset(unserializer.UnserializeOperation(serialized[OPERATION])); - originalInputs_.reset(JobOperationValues::Unserialize - (unserializer, serialized[ORIGINAL_INPUTS])); - workInputs_.reset(JobOperationValues::Unserialize - (unserializer, serialized[WORK_INPUTS])); - } - }; - - - SequenceOfOperationsJob::SequenceOfOperationsJob() : - done_(false), - current_(0), - trailingTimeout_(boost::posix_time::milliseconds(1000)) - { - } - - - SequenceOfOperationsJob::~SequenceOfOperationsJob() - { - for (size_t i = 0; i < operations_.size(); i++) - { - if (operations_[i] != NULL) - { - delete operations_[i]; - } - } - } - - - void SequenceOfOperationsJob::SetDescription(const std::string& description) - { - boost::mutex::scoped_lock lock(mutex_); - description_ = description; - } - - - void SequenceOfOperationsJob::GetDescription(std::string& description) - { - boost::mutex::scoped_lock lock(mutex_); - description = description_; - } - - - void SequenceOfOperationsJob::Register(IObserver& observer) - { - boost::mutex::scoped_lock lock(mutex_); - observers_.push_back(&observer); - } - - - void SequenceOfOperationsJob::Lock::SetTrailingOperationTimeout(unsigned int timeout) - { - that_.trailingTimeout_ = boost::posix_time::milliseconds(timeout); - } - - - size_t SequenceOfOperationsJob::Lock::AddOperation(IJobOperation* operation) - { - if (IsDone()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - size_t index = that_.operations_.size(); - - that_.operations_.push_back(new Operation(index, operation)); - that_.operationAdded_.notify_one(); - - return index; - } - - - void SequenceOfOperationsJob::Lock::AddInput(size_t index, - const JobOperationValue& value) - { - if (IsDone()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else if (index >= that_.operations_.size() || - index < that_.current_) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - that_.operations_[index]->AddOriginalInput(value); - } - } - - - void SequenceOfOperationsJob::Lock::Connect(size_t input, - size_t output) - { - if (IsDone()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else if (input >= output || - input >= that_.operations_.size() || - output >= that_.operations_.size() || - input < that_.current_ || - output < that_.current_) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - Operation& a = *that_.operations_[input]; - Operation& b = *that_.operations_[output]; - a.AddNextOperation(b, false /* not unserializing */); - } - } - - - JobStepResult SequenceOfOperationsJob::Step(const std::string& jobId) - { - boost::mutex::scoped_lock lock(mutex_); - - if (current_ == operations_.size()) - { - LOG(INFO) << "Executing the trailing timeout in the sequence of operations"; - operationAdded_.timed_wait(lock, trailingTimeout_); - - if (current_ == operations_.size()) - { - // No operation was added during the trailing timeout: The - // job is over - LOG(INFO) << "The sequence of operations is over"; - done_ = true; - - for (std::list::iterator it = observers_.begin(); - it != observers_.end(); ++it) - { - (*it)->SignalDone(*this); - } - - return JobStepResult::Success(); - } - else - { - LOG(INFO) << "New operation were added to the sequence of operations"; - } - } - - assert(current_ < operations_.size()); - - while (current_ < operations_.size() && - operations_[current_]->IsDone()) - { - current_++; - } - - if (current_ < operations_.size()) - { - operations_[current_]->Step(); - } - - return JobStepResult::Continue(); - } - - - void SequenceOfOperationsJob::Reset() - { - boost::mutex::scoped_lock lock(mutex_); - - current_ = 0; - done_ = false; - - for (size_t i = 0; i < operations_.size(); i++) - { - operations_[i]->Reset(); - } - } - - - float SequenceOfOperationsJob::GetProgress() - { - boost::mutex::scoped_lock lock(mutex_); - - return (static_cast(current_) / - static_cast(operations_.size() + 1)); - } - - - void SequenceOfOperationsJob::GetPublicContent(Json::Value& value) - { - boost::mutex::scoped_lock lock(mutex_); - - value["CountOperations"] = static_cast(operations_.size()); - value["Description"] = description_; - } - - - bool SequenceOfOperationsJob::Serialize(Json::Value& value) - { - boost::mutex::scoped_lock lock(mutex_); - - value = Json::objectValue; - - std::string jobType; - GetJobType(jobType); - value[TYPE] = jobType; - - value[DESCRIPTION] = description_; - value[TRAILING_TIMEOUT] = static_cast(trailingTimeout_.total_milliseconds()); - value[CURRENT] = static_cast(current_); - - Json::Value tmp = Json::arrayValue; - for (size_t i = 0; i < operations_.size(); i++) - { - Json::Value operation = Json::objectValue; - operations_[i]->Serialize(operation); - tmp.append(operation); - } - - value[OPERATIONS] = tmp; - - return true; - } - - - SequenceOfOperationsJob::SequenceOfOperationsJob(IJobUnserializer& unserializer, - const Json::Value& serialized) : - done_(false) - { - std::string jobType; - GetJobType(jobType); - - if (SerializationToolbox::ReadString(serialized, TYPE) != jobType || - !serialized.isMember(OPERATIONS) || - serialized[OPERATIONS].type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - description_ = SerializationToolbox::ReadString(serialized, DESCRIPTION); - trailingTimeout_ = boost::posix_time::milliseconds - (SerializationToolbox::ReadUnsignedInteger(serialized, TRAILING_TIMEOUT)); - current_ = SerializationToolbox::ReadUnsignedInteger(serialized, CURRENT); - - const Json::Value& ops = serialized[OPERATIONS]; - - // Unserialize the individual operations - operations_.reserve(ops.size()); - for (Json::Value::ArrayIndex i = 0; i < ops.size(); i++) - { - operations_.push_back(new Operation(unserializer, i, ops[i])); - } - - // Connect the next operations - for (Json::Value::ArrayIndex i = 0; i < ops.size(); i++) - { - if (!ops[i].isMember(NEXT_OPERATIONS) || - ops[i][NEXT_OPERATIONS].type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - const Json::Value& next = ops[i][NEXT_OPERATIONS]; - for (Json::Value::ArrayIndex j = 0; j < next.size(); j++) - { - if (next[j].type() != Json::intValue || - next[j].asInt() < 0 || - next[j].asUInt() >= operations_.size()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - operations_[i]->AddNextOperation(*operations_[next[j].asUInt()], true); - } - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/SequenceOfOperationsJob.h --- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,159 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../IJob.h" -#include "IJobOperation.h" - -#include -#include - -#include - -namespace Orthanc -{ - class SequenceOfOperationsJob : public IJob - { - public: - class IObserver : public boost::noncopyable - { - public: - virtual ~IObserver() - { - } - - virtual void SignalDone(const SequenceOfOperationsJob& job) = 0; - }; - - private: - class Operation; - - std::string description_; - bool done_; - boost::mutex mutex_; - std::vector operations_; - size_t current_; - boost::condition_variable operationAdded_; - boost::posix_time::time_duration trailingTimeout_; - std::list observers_; - - void NotifyDone() const; - - public: - SequenceOfOperationsJob(); - - SequenceOfOperationsJob(IJobUnserializer& unserializer, - const Json::Value& serialized); - - virtual ~SequenceOfOperationsJob(); - - void SetDescription(const std::string& description); - - void GetDescription(std::string& description); - - void Register(IObserver& observer); - - // This lock allows adding new operations to the end of the job, - // from another thread than the worker thread, after the job has - // been submitted for processing - class Lock : public boost::noncopyable - { - private: - SequenceOfOperationsJob& that_; - boost::mutex::scoped_lock lock_; - - public: - Lock(SequenceOfOperationsJob& that) : - that_(that), - lock_(that.mutex_) - { - } - - bool IsDone() const - { - return that_.done_; - } - - void SetTrailingOperationTimeout(unsigned int timeout); - - size_t AddOperation(IJobOperation* operation); - - size_t GetOperationsCount() const - { - return that_.operations_.size(); - } - - void AddInput(size_t index, - const JobOperationValue& value); - - void Connect(size_t input, - size_t output); - }; - - virtual void Start() ORTHANC_OVERRIDE - { - } - - virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE; - - virtual void Reset() ORTHANC_OVERRIDE; - - virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE - { - } - - virtual float GetProgress() ORTHANC_OVERRIDE; - - virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE - { - target = "SequenceOfOperations"; - } - - virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE; - - virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE; - - virtual bool GetOutput(std::string& output, - MimeType& mime, - const std::string& key) ORTHANC_OVERRIDE - { - return false; - } - - void AwakeTrailingSleep() - { - operationAdded_.notify_one(); - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/Operations/StringOperationValue.h --- a/Core/JobsEngine/Operations/StringOperationValue.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "JobOperationValue.h" - -#include - -namespace Orthanc -{ - class StringOperationValue : public JobOperationValue - { - private: - std::string content_; - - public: - StringOperationValue(const std::string& content) : - JobOperationValue(JobOperationValue::Type_String), - content_(content) - { - } - - virtual JobOperationValue* Clone() const - { - return new StringOperationValue(content_); - } - - const std::string& GetContent() const - { - return content_; - } - - virtual void Serialize(Json::Value& target) const - { - target = Json::objectValue; - target["Type"] = "String"; - target["Content"] = content_; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/SetOfCommandsJob.cpp --- a/Core/JobsEngine/SetOfCommandsJob.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,303 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "SetOfCommandsJob.h" - -#include "../Logging.h" -#include "../OrthancException.h" -#include "../SerializationToolbox.h" - -#include -#include - -namespace Orthanc -{ - SetOfCommandsJob::SetOfCommandsJob() : - started_(false), - permissive_(false), - position_(0) - { - } - - - SetOfCommandsJob::~SetOfCommandsJob() - { - for (size_t i = 0; i < commands_.size(); i++) - { - assert(commands_[i] != NULL); - delete commands_[i]; - } - } - - - void SetOfCommandsJob::Reserve(size_t size) - { - if (started_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - commands_.reserve(size); - } - } - - - void SetOfCommandsJob::AddCommand(ICommand* command) - { - if (command == NULL) - { - throw OrthancException(ErrorCode_NullPointer); - } - else if (started_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - commands_.push_back(command); - } - } - - - void SetOfCommandsJob::SetPermissive(bool permissive) - { - if (started_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - else - { - permissive_ = permissive; - } - } - - - void SetOfCommandsJob::Reset() - { - if (started_) - { - position_ = 0; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - float SetOfCommandsJob::GetProgress() - { - if (commands_.empty()) - { - return 1; - } - else - { - return (static_cast(position_) / - static_cast(commands_.size())); - } - } - - - const SetOfCommandsJob::ICommand& SetOfCommandsJob::GetCommand(size_t index) const - { - if (index >= commands_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - assert(commands_[index] != NULL); - return *commands_[index]; - } - } - - - JobStepResult SetOfCommandsJob::Step(const std::string& jobId) - { - if (!started_) - { - throw OrthancException(ErrorCode_InternalError); - } - - if (commands_.empty() && - position_ == 0) - { - // No command to handle: We're done - position_ = 1; - return JobStepResult::Success(); - } - - if (position_ >= commands_.size()) - { - // Already done - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - try - { - // Not at the trailing step: Handle the current command - if (!commands_[position_]->Execute(jobId)) - { - // Error - if (!permissive_) - { - return JobStepResult::Failure(ErrorCode_InternalError, NULL); - } - } - } - catch (OrthancException& e) - { - if (permissive_) - { - LOG(WARNING) << "Ignoring an error in a permissive job: " << e.What(); - } - else - { - return JobStepResult::Failure(e); - } - } - - position_ += 1; - - if (position_ == commands_.size()) - { - // We're done - return JobStepResult::Success(); - } - else - { - return JobStepResult::Continue(); - } - } - - - - static const char* KEY_DESCRIPTION = "Description"; - static const char* KEY_PERMISSIVE = "Permissive"; - static const char* KEY_POSITION = "Position"; - static const char* KEY_TYPE = "Type"; - static const char* KEY_COMMANDS = "Commands"; - - - void SetOfCommandsJob::GetPublicContent(Json::Value& value) - { - value[KEY_DESCRIPTION] = GetDescription(); - } - - - bool SetOfCommandsJob::Serialize(Json::Value& target) - { - target = Json::objectValue; - - std::string type; - GetJobType(type); - target[KEY_TYPE] = type; - - target[KEY_PERMISSIVE] = permissive_; - target[KEY_POSITION] = static_cast(position_); - target[KEY_DESCRIPTION] = description_; - - target[KEY_COMMANDS] = Json::arrayValue; - Json::Value& tmp = target[KEY_COMMANDS]; - - for (size_t i = 0; i < commands_.size(); i++) - { - assert(commands_[i] != NULL); - - Json::Value command; - commands_[i]->Serialize(command); - tmp.append(command); - } - - return true; - } - - - SetOfCommandsJob::SetOfCommandsJob(ICommandUnserializer* unserializer, - const Json::Value& source) : - started_(false) - { - std::unique_ptr raii(unserializer); - - permissive_ = SerializationToolbox::ReadBoolean(source, KEY_PERMISSIVE); - position_ = SerializationToolbox::ReadUnsignedInteger(source, KEY_POSITION); - description_ = SerializationToolbox::ReadString(source, KEY_DESCRIPTION); - - if (!source.isMember(KEY_COMMANDS) || - source[KEY_COMMANDS].type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - const Json::Value& tmp = source[KEY_COMMANDS]; - commands_.resize(tmp.size()); - - for (Json::Value::ArrayIndex i = 0; i < tmp.size(); i++) - { - try - { - commands_[i] = unserializer->Unserialize(tmp[i]); - } - catch (OrthancException&) - { - } - - if (commands_[i] == NULL) - { - for (size_t j = 0; j < i; j++) - { - delete commands_[j]; - } - - throw OrthancException(ErrorCode_BadFileFormat); - } - } - } - - if (commands_.empty()) - { - if (position_ > 1) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - else if (position_ > commands_.size()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/SetOfCommandsJob.h --- a/Core/JobsEngine/SetOfCommandsJob.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IJob.h" - -#include - -namespace Orthanc -{ - class SetOfCommandsJob : public IJob - { - public: - class ICommand : public boost::noncopyable - { - public: - virtual ~ICommand() - { - } - - virtual bool Execute(const std::string& jobId) = 0; - - virtual void Serialize(Json::Value& target) const = 0; - }; - - class ICommandUnserializer : public boost::noncopyable - { - public: - virtual ~ICommandUnserializer() - { - } - - virtual ICommand* Unserialize(const Json::Value& source) const = 0; - }; - - private: - bool started_; - std::vector commands_; - bool permissive_; - size_t position_; - std::string description_; - - public: - SetOfCommandsJob(); - - SetOfCommandsJob(ICommandUnserializer* unserializer /* takes ownership */, - const Json::Value& source); - - virtual ~SetOfCommandsJob(); - - size_t GetPosition() const - { - return position_; - } - - void SetDescription(const std::string& description) - { - description_ = description; - } - - const std::string& GetDescription() const - { - return description_; - } - - void Reserve(size_t size); - - size_t GetCommandsCount() const - { - return commands_.size(); - } - - void AddCommand(ICommand* command); // Takes ownership - - bool IsPermissive() const - { - return permissive_; - } - - void SetPermissive(bool permissive); - - virtual void Reset() ORTHANC_OVERRIDE; - - virtual void Start() ORTHANC_OVERRIDE - { - started_ = true; - } - - virtual float GetProgress() ORTHANC_OVERRIDE; - - bool IsStarted() const - { - return started_; - } - - const ICommand& GetCommand(size_t index) const; - - virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE; - - virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE; - - virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE; - - virtual bool GetOutput(std::string& output, - MimeType& mime, - const std::string& key) ORTHANC_OVERRIDE - { - return false; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/SetOfInstancesJob.cpp --- a/Core/JobsEngine/SetOfInstancesJob.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,252 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "SetOfInstancesJob.h" - -#include "../OrthancException.h" -#include "../SerializationToolbox.h" - -#include - -namespace Orthanc -{ - class SetOfInstancesJob::InstanceCommand : public SetOfInstancesJob::ICommand - { - private: - SetOfInstancesJob& that_; - std::string instance_; - - public: - InstanceCommand(SetOfInstancesJob& that, - const std::string& instance) : - that_(that), - instance_(instance) - { - } - - const std::string& GetInstance() const - { - return instance_; - } - - virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE - { - if (!that_.HandleInstance(instance_)) - { - that_.failedInstances_.insert(instance_); - return false; - } - else - { - return true; - } - } - - virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE - { - target = instance_; - } - }; - - - class SetOfInstancesJob::TrailingStepCommand : public SetOfInstancesJob::ICommand - { - private: - SetOfInstancesJob& that_; - - public: - TrailingStepCommand(SetOfInstancesJob& that) : - that_(that) - { - } - - virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE - { - return that_.HandleTrailingStep(); - } - - virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE - { - target = Json::nullValue; - } - }; - - - class SetOfInstancesJob::InstanceUnserializer : - public SetOfInstancesJob::ICommandUnserializer - { - private: - SetOfInstancesJob& that_; - - public: - InstanceUnserializer(SetOfInstancesJob& that) : - that_(that) - { - } - - virtual ICommand* Unserialize(const Json::Value& source) const - { - if (source.type() == Json::nullValue) - { - return new TrailingStepCommand(that_); - } - else if (source.type() == Json::stringValue) - { - return new InstanceCommand(that_, source.asString()); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - }; - - - SetOfInstancesJob::SetOfInstancesJob() : - hasTrailingStep_(false) - { - } - - - void SetOfInstancesJob::AddInstance(const std::string& instance) - { - AddCommand(new InstanceCommand(*this, instance)); - } - - - void SetOfInstancesJob::AddTrailingStep() - { - AddCommand(new TrailingStepCommand(*this)); - hasTrailingStep_ = true; - } - - - size_t SetOfInstancesJob::GetInstancesCount() const - { - if (hasTrailingStep_) - { - assert(GetCommandsCount() > 0); - return GetCommandsCount() - 1; - } - else - { - return GetCommandsCount(); - } - } - - - const std::string& SetOfInstancesJob::GetInstance(size_t index) const - { - if (index >= GetInstancesCount()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - return dynamic_cast(GetCommand(index)).GetInstance(); - } - } - - - void SetOfInstancesJob::Start() - { - SetOfCommandsJob::Start(); - } - - - void SetOfInstancesJob::Reset() - { - SetOfCommandsJob::Reset(); - - failedInstances_.clear(); - } - - - static const char* KEY_TRAILING_STEP = "TrailingStep"; - static const char* KEY_FAILED_INSTANCES = "FailedInstances"; - static const char* KEY_PARENT_RESOURCES = "ParentResources"; - - void SetOfInstancesJob::GetPublicContent(Json::Value& target) - { - SetOfCommandsJob::GetPublicContent(target); - target["InstancesCount"] = static_cast(GetInstancesCount()); - target["FailedInstancesCount"] = static_cast(failedInstances_.size()); - - if (!parentResources_.empty()) - { - SerializationToolbox::WriteSetOfStrings(target, parentResources_, KEY_PARENT_RESOURCES); - } - } - - - bool SetOfInstancesJob::Serialize(Json::Value& target) - { - if (SetOfCommandsJob::Serialize(target)) - { - target[KEY_TRAILING_STEP] = hasTrailingStep_; - SerializationToolbox::WriteSetOfStrings(target, failedInstances_, KEY_FAILED_INSTANCES); - SerializationToolbox::WriteSetOfStrings(target, parentResources_, KEY_PARENT_RESOURCES); - return true; - } - else - { - return false; - } - } - - - SetOfInstancesJob::SetOfInstancesJob(const Json::Value& source) : - SetOfCommandsJob(new InstanceUnserializer(*this), source) - { - SerializationToolbox::ReadSetOfStrings(failedInstances_, source, KEY_FAILED_INSTANCES); - - if (source.isMember(KEY_PARENT_RESOURCES)) - { - // Backward compatibility with Orthanc <= 1.5.6 - SerializationToolbox::ReadSetOfStrings(parentResources_, source, KEY_PARENT_RESOURCES); - } - - if (source.isMember(KEY_TRAILING_STEP)) - { - hasTrailingStep_ = SerializationToolbox::ReadBoolean(source, KEY_TRAILING_STEP); - } - else - { - // Backward compatibility with Orthanc <= 1.4.2 - hasTrailingStep_ = false; - } - } - - -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/JobsEngine/SetOfInstancesJob.h --- a/Core/JobsEngine/SetOfInstancesJob.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IJob.h" -#include "SetOfCommandsJob.h" - -#include - -namespace Orthanc -{ - class SetOfInstancesJob : public SetOfCommandsJob - { - private: - class InstanceCommand; - class TrailingStepCommand; - class InstanceUnserializer; - - bool hasTrailingStep_; - std::set failedInstances_; - std::set parentResources_; - - protected: - virtual bool HandleInstance(const std::string& instance) = 0; - - virtual bool HandleTrailingStep() = 0; - - // Hiding this method, use AddInstance() instead - using SetOfCommandsJob::AddCommand; - - public: - SetOfInstancesJob(); - - SetOfInstancesJob(const Json::Value& source); // Unserialization - - // Only used for reporting in the public content - // https://groups.google.com/d/msg/orthanc-users/9GCV88GLEzw/6wAgP_PRAgAJ - void AddParentResource(const std::string& resource) - { - parentResources_.insert(resource); - } - - void AddInstance(const std::string& instance); - - void AddTrailingStep(); - - size_t GetInstancesCount() const; - - const std::string& GetInstance(size_t index) const; - - bool HasTrailingStep() const - { - return hasTrailingStep_; - } - - const std::set& GetFailedInstances() const - { - return failedInstances_; - } - - bool IsFailedInstance(const std::string& instance) const - { - return failedInstances_.find(instance) != failedInstances_.end(); - } - - virtual void Start(); - - virtual void Reset(); - - virtual void GetPublicContent(Json::Value& target); - - virtual bool Serialize(Json::Value& target); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Logging.cpp --- a/Core/Logging.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,817 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "Logging.h" - -#include "OrthancException.h" - - -namespace Orthanc -{ - namespace Logging - { - const char* EnumerationToString(LogLevel level) - { - switch (level) - { - case LogLevel_ERROR: - return "ERROR"; - - case LogLevel_WARNING: - return "WARNING"; - - case LogLevel_INFO: - return "INFO"; - - case LogLevel_TRACE: - return "TRACE"; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - LogLevel StringToLogLevel(const char *level) - { - if (strcmp(level, "ERROR") == 0) - { - return LogLevel_ERROR; - } - else if (strcmp(level, "WARNING") == 0) - { - return LogLevel_WARNING; - } - else if (strcmp(level, "INFO") == 0) - { - return LogLevel_INFO; - } - else if (strcmp(level, "TRACE") == 0) - { - return LogLevel_TRACE; - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - } - } -} - - -#if ORTHANC_ENABLE_LOGGING != 1 - -namespace Orthanc -{ - namespace Logging - { - void InitializePluginContext(void* pluginContext) - { - } - - void Initialize() - { - } - - void Finalize() - { - } - - void Reset() - { - } - - void Flush() - { - } - - void EnableInfoLevel(bool enabled) - { - } - - void EnableTraceLevel(bool enabled) - { - } - - bool IsTraceLevelEnabled() - { - return false; - } - - bool IsInfoLevelEnabled() - { - return false; - } - - void SetTargetFile(const std::string& path) - { - } - - void SetTargetFolder(const std::string& path) - { - } - } -} - - -#elif ORTHANC_ENABLE_LOGGING_STDIO == 1 - -/********************************************************* - * Logger compatible with OR logger that sends its - * output to the emscripten html5 api (depending on the - * definition of __EMSCRIPTEN__) - *********************************************************/ - -#include - -#ifdef __EMSCRIPTEN__ -# include -#endif - -namespace Orthanc -{ - namespace Logging - { - static bool infoEnabled_ = false; - static bool traceEnabled_ = false; - -#ifdef __EMSCRIPTEN__ - static void ErrorLogFunc(const char* msg) - { - emscripten_console_error(msg); - } - - static void WarningLogFunc(const char* msg) - { - emscripten_console_warn(msg); - } - - static void InfoLogFunc(const char* msg) - { - emscripten_console_log(msg); - } - - static void TraceLogFunc(const char* msg) - { - emscripten_console_log(msg); - } -#else /* __EMSCRIPTEN__ not #defined */ - static void ErrorLogFunc(const char* msg) - { - fprintf(stderr, "E: %s\n", msg); - } - - static void WarningLogFunc(const char*) - { - fprintf(stdout, "W: %s\n", msg); - } - - static void InfoLogFunc(const char*) - { - fprintf(stdout, "I: %s\n", msg); - } - - static void TraceLogFunc(const char*) - { - fprintf(stdout, "T: %s\n", msg); - } -#endif /* __EMSCRIPTEN__ */ - - - InternalLogger::~InternalLogger() - { - std::string message = messageStream_.str(); - - switch (level_) - { - case LogLevel_ERROR: - ErrorLogFunc(message.c_str()); - break; - - case LogLevel_WARNING: - WarningLogFunc(message.c_str()); - break; - - case LogLevel_INFO: - if (infoEnabled_) - { - InfoLogFunc(message.c_str()); - // TODO: stone_console_info(message_.c_str()); - } - break; - - case LogLevel_TRACE: - if (traceEnabled_) - { - TraceLogFunc(message.c_str()); - } - break; - - default: - { - std::stringstream ss; - ss << "Unknown log level (" << level_ << ") for message: " << message; - std::string s = ss.str(); - ErrorLogFunc(s.c_str()); - } - } - } - - void InitializePluginContext(void* pluginContext) - { - } - - void Initialize() - { - } - - void Finalize() - { - } - - void Reset() - { - } - - void Flush() - { - } - - void EnableInfoLevel(bool enabled) - { - infoEnabled_ = enabled; - - if (!enabled) - { - // Also disable the "TRACE" level when info-level debugging is disabled - traceEnabled_ = false; - } - } - - bool IsInfoLevelEnabled() - { - return infoEnabled_; - } - - void EnableTraceLevel(bool enabled) - { - traceEnabled_ = enabled; - } - - bool IsTraceLevelEnabled() - { - return traceEnabled_; - } - - void SetTargetFile(const std::string& path) - { - } - - void SetTargetFolder(const std::string& path) - { - } - } -} - - -#else - -/********************************************************* - * Logger compatible with the Orthanc plugin SDK, or that - * mimics behavior from Google Log. - *********************************************************/ - -#include - -namespace -{ - /** - * This is minimal implementation of the context for an Orthanc - * plugin, limited to the logging facilities, and that is binary - * compatible with the definitions of "OrthancCPlugin.h" - **/ - typedef enum - { - _OrthancPluginService_LogInfo = 1, - _OrthancPluginService_LogWarning = 2, - _OrthancPluginService_LogError = 3, - _OrthancPluginService_INTERNAL = 0x7fffffff - } _OrthancPluginService; - - typedef struct _OrthancPluginContext_t - { - void* pluginsManager; - const char* orthancVersion; - void (*Free) (void* buffer); - int32_t (*InvokeService) (struct _OrthancPluginContext_t* context, - _OrthancPluginService service, - const void* params); - } OrthancPluginContext; -} - - -#include "Enumerations.h" -#include "SystemToolbox.h" - -#include -#include -#include -#include - - -namespace -{ - struct LoggingStreamsContext - { - std::string targetFile_; - std::string targetFolder_; - - std::ostream* error_; - std::ostream* warning_; - std::ostream* info_; - - std::unique_ptr file_; - - LoggingStreamsContext() : - error_(&std::cerr), - warning_(&std::cerr), - info_(&std::cerr) - { - } - }; -} - - - -static std::unique_ptr loggingStreamsContext_; -static boost::mutex loggingStreamsMutex_; -static Orthanc::Logging::NullStream nullStream_; -static OrthancPluginContext* pluginContext_ = NULL; -static bool infoEnabled_ = false; -static bool traceEnabled_ = false; - - -namespace Orthanc -{ - namespace Logging - { - static void GetLogPath(boost::filesystem::path& log, - boost::filesystem::path& link, - const std::string& suffix, - const std::string& directory) - { - /** - From Google Log documentation: - - Unless otherwise specified, logs will be written to the filename - "...log.", - followed by the date, time, and pid (you can't prevent the date, - time, and pid from being in the filename). - - In this implementation : "hostname" and "username" are not used - **/ - - boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); - boost::filesystem::path root(directory); - boost::filesystem::path exe(SystemToolbox::GetPathToExecutable()); - - if (!boost::filesystem::exists(root) || - !boost::filesystem::is_directory(root)) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - - char date[64]; - sprintf(date, "%04d%02d%02d-%02d%02d%02d.%d", - static_cast(now.date().year()), - now.date().month().as_number(), - now.date().day().as_number(), - static_cast(now.time_of_day().hours()), - static_cast(now.time_of_day().minutes()), - static_cast(now.time_of_day().seconds()), - SystemToolbox::GetProcessId()); - - std::string programName = exe.filename().replace_extension("").string(); - - log = (root / (programName + ".log" + suffix + "." + std::string(date))); - link = (root / (programName + ".log" + suffix)); - } - - - static void PrepareLogFolder(std::unique_ptr& file, - const std::string& suffix, - const std::string& directory) - { - boost::filesystem::path log, link; - GetLogPath(log, link, suffix, directory); - -#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))) - boost::filesystem::remove(link); - boost::filesystem::create_symlink(log.filename(), link); -#endif - - file.reset(new std::ofstream(log.string().c_str())); - } - - - // "loggingStreamsMutex_" must be locked - static void CheckFile(std::unique_ptr& f) - { - if (loggingStreamsContext_->file_.get() == NULL || - !loggingStreamsContext_->file_->is_open()) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - } - - - static void GetLinePrefix(std::string& prefix, - LogLevel level, - const char* file, - int line) - { - boost::filesystem::path path(file); - boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time(); - boost::posix_time::time_duration duration = now.time_of_day(); - - /** - From Google Log documentation: - - "Log lines have this form: - - Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... - - where the fields are defined as follows: - - L A single character, representing the log level (eg 'I' for INFO) - mm The month (zero padded; ie May is '05') - dd The day (zero padded) - hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds - threadid The space-padded thread ID as returned by GetTID() (this matches the PID on Linux) - file The file name - line The line number - msg The user-supplied message" - - In this implementation, "threadid" is not printed. - **/ - - char c; - switch (level) - { - case LogLevel_ERROR: - c = 'E'; - break; - - case LogLevel_WARNING: - c = 'W'; - break; - - case LogLevel_INFO: - c = 'I'; - break; - - case LogLevel_TRACE: - c = 'T'; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - char date[64]; - sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ", - c, - now.date().month().as_number(), - now.date().day().as_number(), - static_cast(duration.hours()), - static_cast(duration.minutes()), - static_cast(duration.seconds()), - static_cast(duration.fractional_seconds())); - - prefix = (std::string(date) + path.filename().string() + ":" + - boost::lexical_cast(line) + "] "); - } - - - void InitializePluginContext(void* pluginContext) - { - assert(sizeof(_OrthancPluginService) == sizeof(int32_t)); - - boost::mutex::scoped_lock lock(loggingStreamsMutex_); - loggingStreamsContext_.reset(NULL); - pluginContext_ = reinterpret_cast(pluginContext); - } - - - void Initialize() - { - boost::mutex::scoped_lock lock(loggingStreamsMutex_); - - if (loggingStreamsContext_.get() == NULL) - { - loggingStreamsContext_.reset(new LoggingStreamsContext); - } - } - - void Finalize() - { - boost::mutex::scoped_lock lock(loggingStreamsMutex_); - loggingStreamsContext_.reset(NULL); - } - - void Reset() - { - // Recover the old logging context - std::unique_ptr old; - - { - boost::mutex::scoped_lock lock(loggingStreamsMutex_); - if (loggingStreamsContext_.get() == NULL) - { - return; - } - else - { -#if __cplusplus < 201103L - old.reset(loggingStreamsContext_.release()); -#else - old = std::move(loggingStreamsContext_); -#endif - - // Create a new logging context, - loggingStreamsContext_.reset(new LoggingStreamsContext); - } - } - - if (!old->targetFolder_.empty()) - { - SetTargetFolder(old->targetFolder_); - } - else if (!old->targetFile_.empty()) - { - SetTargetFile(old->targetFile_); - } - } - - - void EnableInfoLevel(bool enabled) - { - infoEnabled_ = enabled; - - if (!enabled) - { - // Also disable the "TRACE" level when info-level debugging is disabled - traceEnabled_ = false; - } - } - - bool IsInfoLevelEnabled() - { - return infoEnabled_; - } - - void EnableTraceLevel(bool enabled) - { - traceEnabled_ = enabled; - - if (enabled) - { - // Also enable the "INFO" level when trace-level debugging is enabled - infoEnabled_ = true; - } - } - - bool IsTraceLevelEnabled() - { - return traceEnabled_; - } - - - void SetTargetFolder(const std::string& path) - { - boost::mutex::scoped_lock lock(loggingStreamsMutex_); - if (loggingStreamsContext_.get() != NULL) - { - PrepareLogFolder(loggingStreamsContext_->file_, "" /* no suffix */, path); - CheckFile(loggingStreamsContext_->file_); - - loggingStreamsContext_->targetFile_.clear(); - loggingStreamsContext_->targetFolder_ = path; - loggingStreamsContext_->warning_ = loggingStreamsContext_->file_.get(); - loggingStreamsContext_->error_ = loggingStreamsContext_->file_.get(); - loggingStreamsContext_->info_ = loggingStreamsContext_->file_.get(); - } - } - - - void SetTargetFile(const std::string& path) - { - boost::mutex::scoped_lock lock(loggingStreamsMutex_); - - if (loggingStreamsContext_.get() != NULL) - { - loggingStreamsContext_->file_.reset(new std::ofstream(path.c_str(), std::fstream::app)); - CheckFile(loggingStreamsContext_->file_); - - loggingStreamsContext_->targetFile_ = path; - loggingStreamsContext_->targetFolder_.clear(); - loggingStreamsContext_->warning_ = loggingStreamsContext_->file_.get(); - loggingStreamsContext_->error_ = loggingStreamsContext_->file_.get(); - loggingStreamsContext_->info_ = loggingStreamsContext_->file_.get(); - } - } - - - InternalLogger::InternalLogger(LogLevel level, - const char* file, - int line) : - lock_(loggingStreamsMutex_, boost::defer_lock_t()), - level_(level), - stream_(&nullStream_) // By default, logging to "/dev/null" is simulated - { - if (pluginContext_ != NULL) - { - // We are logging using the Orthanc plugin SDK - - if (level == LogLevel_TRACE) - { - // No trace level in plugins, directly exit as the stream is - // set to "/dev/null" - return; - } - else - { - pluginStream_.reset(new std::stringstream); - stream_ = pluginStream_.get(); - } - } - else - { - // We are logging in a standalone application, not inside an Orthanc plugin - - if ((level == LogLevel_INFO && !infoEnabled_) || - (level == LogLevel_TRACE && !traceEnabled_)) - { - // This logging level is disabled, directly exit as the - // stream is set to "/dev/null" - return; - } - - std::string prefix; - GetLinePrefix(prefix, level, file, line); - - { - // We lock the global mutex. The mutex is locked until the - // destructor is called: No change in the output can be done. - lock_.lock(); - - if (loggingStreamsContext_.get() == NULL) - { - fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n"); - lock_.unlock(); - return; - } - - switch (level) - { - case LogLevel_ERROR: - stream_ = loggingStreamsContext_->error_; - break; - - case LogLevel_WARNING: - stream_ = loggingStreamsContext_->warning_; - break; - - case LogLevel_INFO: - case LogLevel_TRACE: - stream_ = loggingStreamsContext_->info_; - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } - - if (stream_ == &nullStream_) - { - // The logging is disabled for this level, we can release - // the global mutex. - lock_.unlock(); - } - else - { - try - { - (*stream_) << prefix; - } - catch (...) - { - // Something is going really wrong, probably running out of - // memory. Fallback to a degraded mode. - stream_ = loggingStreamsContext_->error_; - (*stream_) << "E???? ??:??:??.?????? ] "; - } - } - } - } - } - - - InternalLogger::~InternalLogger() - { - if (pluginStream_.get() != NULL) - { - // We are logging through the Orthanc SDK - - std::string message = pluginStream_->str(); - - if (pluginContext_ != NULL) - { - switch (level_) - { - case LogLevel_ERROR: - pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_LogError, message.c_str()); - break; - - case LogLevel_WARNING: - pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_LogWarning, message.c_str()); - break; - - case LogLevel_INFO: - pluginContext_->InvokeService(pluginContext_, _OrthancPluginService_LogInfo, message.c_str()); - break; - - default: - break; - } - } - } - else if (stream_ != &nullStream_) - { - *stream_ << "\n"; - stream_->flush(); - } - } - - - void Flush() - { - if (pluginContext_ != NULL) - { - boost::mutex::scoped_lock lock(loggingStreamsMutex_); - - if (loggingStreamsContext_.get() != NULL && - loggingStreamsContext_->file_.get() != NULL) - { - loggingStreamsContext_->file_->flush(); - } - } - } - - - void SetErrorWarnInfoLoggingStreams(std::ostream& errorStream, - std::ostream& warningStream, - std::ostream& infoStream) - { - boost::mutex::scoped_lock lock(loggingStreamsMutex_); - - loggingStreamsContext_.reset(new LoggingStreamsContext); - loggingStreamsContext_->error_ = &errorStream; - loggingStreamsContext_->warning_ = &warningStream; - loggingStreamsContext_->info_ = &infoStream; - } - } -} - - -#endif // ORTHANC_ENABLE_LOGGING diff -r 6c6239aec462 -r d25f4c0fa160 Core/Logging.h --- a/Core/Logging.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,221 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -// To have ORTHANC_ENABLE_LOGGING defined if using the shared library -#include "OrthancFramework.h" - -#include - -#if !defined(ORTHANC_ENABLE_LOGGING) -# error The macro ORTHANC_ENABLE_LOGGING must be defined -#endif - -#if !defined(ORTHANC_ENABLE_LOGGING_STDIO) -# if ORTHANC_ENABLE_LOGGING == 1 -# error The macro ORTHANC_ENABLE_LOGGING_STDIO must be defined -# else -# define ORTHANC_ENABLE_LOGGING_STDIO 0 -# endif -#endif - - -namespace Orthanc -{ - namespace Logging - { - enum LogLevel - { - LogLevel_ERROR, - LogLevel_WARNING, - LogLevel_INFO, - LogLevel_TRACE - }; - - ORTHANC_PUBLIC const char* EnumerationToString(LogLevel level); - - ORTHANC_PUBLIC LogLevel StringToLogLevel(const char* level); - - // "pluginContext" must be of type "OrthancPluginContext" - ORTHANC_PUBLIC void InitializePluginContext(void* pluginContext); - - ORTHANC_PUBLIC void Initialize(); - - ORTHANC_PUBLIC void Finalize(); - - ORTHANC_PUBLIC void Reset(); - - ORTHANC_PUBLIC void Flush(); - - ORTHANC_PUBLIC void EnableInfoLevel(bool enabled); - - ORTHANC_PUBLIC void EnableTraceLevel(bool enabled); - - ORTHANC_PUBLIC bool IsTraceLevelEnabled(); - - ORTHANC_PUBLIC bool IsInfoLevelEnabled(); - - ORTHANC_PUBLIC void SetTargetFile(const std::string& path); - - ORTHANC_PUBLIC void SetTargetFolder(const std::string& path); - - struct NullStream : public std::ostream - { - NullStream() : - std::ios(0), - std::ostream(0) - { - } - - template - std::ostream& operator<< (const T& message) - { - return *this; - } - }; - } -} - - -#if ORTHANC_ENABLE_LOGGING != 1 -# define LOG(level) ::Orthanc::Logging::NullStream() -# define VLOG(level) ::Orthanc::Logging::NullStream() -#else /* ORTHANC_ENABLE_LOGGING == 1 */ -# define LOG(level) ::Orthanc::Logging::InternalLogger \ - (::Orthanc::Logging::LogLevel_ ## level, __FILE__, __LINE__) -# define VLOG(level) ::Orthanc::Logging::InternalLogger \ - (::Orthanc::Logging::LogLevel_TRACE, __FILE__, __LINE__) -#endif - - - -#if (ORTHANC_ENABLE_LOGGING == 1 && \ - ORTHANC_ENABLE_LOGGING_STDIO == 1) -// This is notably for WebAssembly - -#include -#include -#include - -namespace Orthanc -{ - namespace Logging - { - class ORTHANC_PUBLIC InternalLogger : public boost::noncopyable - { - private: - LogLevel level_; - std::stringstream messageStream_; - - public: - InternalLogger(LogLevel level, - const char* file /* ignored */, - int line /* ignored */) : - level_(level) - { - } - - ~InternalLogger(); - - template - std::ostream& operator<< (const T& message) - { - return messageStream_ << boost::lexical_cast(message); - } - }; - } -} - -#endif - - - -#if (ORTHANC_ENABLE_LOGGING == 1 && \ - ORTHANC_ENABLE_LOGGING_STDIO == 0) - -#include "Compatibility.h" // For std::unique_ptr<> - -#include -#include -#include -#include - -namespace Orthanc -{ - namespace Logging - { - class ORTHANC_PUBLIC InternalLogger : public boost::noncopyable - { - private: - boost::mutex::scoped_lock lock_; - LogLevel level_; - std::unique_ptr pluginStream_; - std::ostream* stream_; - - public: - InternalLogger(LogLevel level, - const char* file, - int line); - - ~InternalLogger(); - - template - std::ostream& operator<< (const T& message) - { - return (*stream_) << boost::lexical_cast(message); - } - }; - - - /** - * Set custom logging streams for the error, warning and info - * logs. This function may not be called if a log file or folder - * has been set beforehand. All three references must be valid. - * - * Please ensure the supplied streams remain alive and valid as - * long as logging calls are performed. In order to prevent - * dangling pointer usage, it is mandatory to call - * Orthanc::Logging::Reset() before the stream objects are - * destroyed and the references become invalid. - * - * This function must only be used by unit tests. It is ignored if - * InitializePluginContext() was called. - **/ - ORTHANC_PUBLIC void SetErrorWarnInfoLoggingStreams(std::ostream& errorStream, - std::ostream& warningStream, - std::ostream& infoStream); - } -} - -#endif diff -r 6c6239aec462 -r d25f4c0fa160 Core/Lua/LuaContext.cpp --- a/Core/Lua/LuaContext.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,690 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "LuaContext.h" - -#include "../Logging.h" -#include "../OrthancException.h" -#include "../Toolbox.h" - -#include -#include -#include - -extern "C" -{ -#include -#include -} - -namespace Orthanc -{ - static bool OnlyContainsDigits(const std::string& s) - { - for (size_t i = 0; i < s.size(); i++) - { - if (!isdigit(s[i])) - { - return false; - } - } - - return true; - } - - LuaContext& LuaContext::GetLuaContext(lua_State *state) - { - const void* value = GetGlobalVariable(state, "_LuaContext"); - assert(value != NULL); - - return *const_cast(reinterpret_cast(value)); - } - - int LuaContext::PrintToLog(lua_State *state) - { - LuaContext& that = GetLuaContext(state); - - // http://medek.wordpress.com/2009/02/03/wrapping-lua-errors-and-print-function/ - int nArgs = lua_gettop(state); - lua_getglobal(state, "tostring"); - - // Make sure you start at 1 *NOT* 0 for arrays in Lua. - std::string result; - - for (int i = 1; i <= nArgs; i++) - { - const char *s; - lua_pushvalue(state, -1); - lua_pushvalue(state, i); - lua_call(state, 1, 1); - s = lua_tostring(state, -1); - - if (result.size() > 0) - result.append(", "); - - if (s == NULL) - result.append(""); - else - result.append(s); - - lua_pop(state, 1); - } - - LOG(WARNING) << "Lua says: " << result; - that.log_.append(result); - that.log_.append("\n"); - - return 0; - } - - - int LuaContext::ParseJson(lua_State *state) - { - LuaContext& that = GetLuaContext(state); - - int nArgs = lua_gettop(state); - if (nArgs != 1 || - !lua_isstring(state, 1)) // Password - { - lua_pushnil(state); - return 1; - } - - const char* str = lua_tostring(state, 1); - - Json::Value value; - Json::Reader reader; - if (reader.parse(str, str + strlen(str), value)) - { - that.PushJson(value); - } - else - { - lua_pushnil(state); - } - - return 1; - } - - - int LuaContext::DumpJson(lua_State *state) - { - LuaContext& that = GetLuaContext(state); - - int nArgs = lua_gettop(state); - if ((nArgs != 1 && nArgs != 2) || - (nArgs == 2 && !lua_isboolean(state, 2))) - { - lua_pushnil(state); - return 1; - } - - bool keepStrings = false; - if (nArgs == 2) - { - keepStrings = lua_toboolean(state, 2) ? true : false; - } - - Json::Value json; - that.GetJson(json, state, 1, keepStrings); - - Json::FastWriter writer; - std::string s = writer.write(json); - lua_pushlstring(state, s.c_str(), s.size()); - - return 1; - } - - -#if ORTHANC_ENABLE_CURL == 1 - int LuaContext::SetHttpCredentials(lua_State *state) - { - LuaContext& that = GetLuaContext(state); - - // Check the types of the arguments - int nArgs = lua_gettop(state); - if (nArgs != 2 || - !lua_isstring(state, 1) || // Username - !lua_isstring(state, 2)) // Password - { - LOG(ERROR) << "Lua: Bad parameters to SetHttpCredentials()"; - } - else - { - // Configure the HTTP client - const char* username = lua_tostring(state, 1); - const char* password = lua_tostring(state, 2); - that.httpClient_.SetCredentials(username, password); - } - - return 0; - } -#endif - - -#if ORTHANC_ENABLE_CURL == 1 - bool LuaContext::AnswerHttpQuery(lua_State* state) - { - std::string str; - - try - { - httpClient_.Apply(str); - } - catch (OrthancException&) - { - return false; - } - - // Return the result of the HTTP request - lua_pushlstring(state, str.c_str(), str.size()); - - return true; - } -#endif - - -#if ORTHANC_ENABLE_CURL == 1 - void LuaContext::SetHttpHeaders(int top) - { - std::map headers; - GetDictionaryArgument(headers, lua_, top, false /* keep key case as provided by Lua script */); - - httpClient_.ClearHeaders(); // always reset headers in case they have been set in a previous request - - for (std::map::const_iterator - it = headers.begin(); it != headers.end(); ++it) - { - httpClient_.AddHeader(it->first, it->second); - } - } -#endif - - -#if ORTHANC_ENABLE_CURL == 1 - int LuaContext::CallHttpGet(lua_State *state) - { - LuaContext& that = GetLuaContext(state); - - // Check the types of the arguments - int nArgs = lua_gettop(state); - if (nArgs < 1 || nArgs > 2 || // check args count - !lua_isstring(state, 1)) // URL is a string - { - LOG(ERROR) << "Lua: Bad parameters to HttpGet()"; - lua_pushnil(state); - return 1; - } - - // Configure the HTTP client class - const char* url = lua_tostring(state, 1); - that.httpClient_.SetMethod(HttpMethod_Get); - that.httpClient_.SetUrl(url); - that.httpClient_.GetBody().clear(); - that.SetHttpHeaders(2); - - // Do the HTTP GET request - if (!that.AnswerHttpQuery(state)) - { - LOG(ERROR) << "Lua: Error in HttpGet() for URL " << url; - lua_pushnil(state); - } - - return 1; - } -#endif - - -#if ORTHANC_ENABLE_CURL == 1 - int LuaContext::CallHttpPostOrPut(lua_State *state, - HttpMethod method) - { - LuaContext& that = GetLuaContext(state); - - // Check the types of the arguments - int nArgs = lua_gettop(state); - if ((nArgs < 1 || nArgs > 3) || // check arg count - !lua_isstring(state, 1) || // URL is a string - (nArgs >= 2 && (!lua_isstring(state, 2) && !lua_isnil(state, 2)))) // Body data is null or is a string - { - LOG(ERROR) << "Lua: Bad parameters to HttpPost() or HttpPut()"; - lua_pushnil(state); - return 1; - } - - // Configure the HTTP client class - const char* url = lua_tostring(state, 1); - that.httpClient_.SetMethod(method); - that.httpClient_.SetUrl(url); - that.SetHttpHeaders(3); - - if (nArgs >= 2 && !lua_isnil(state, 2)) - { - size_t bodySize = 0; - const char* bodyData = lua_tolstring(state, 2, &bodySize); - - if (bodySize == 0) - { - that.httpClient_.GetBody().clear(); - } - else - { - that.httpClient_.GetBody().assign(bodyData, bodySize); - } - } - else - { - that.httpClient_.GetBody().clear(); - } - - // Do the HTTP POST/PUT request - if (!that.AnswerHttpQuery(state)) - { - LOG(ERROR) << "Lua: Error in HttpPost() or HttpPut() for URL " << url; - lua_pushnil(state); - } - - return 1; - } -#endif - - -#if ORTHANC_ENABLE_CURL == 1 - int LuaContext::CallHttpPost(lua_State *state) - { - return CallHttpPostOrPut(state, HttpMethod_Post); - } -#endif - - -#if ORTHANC_ENABLE_CURL == 1 - int LuaContext::CallHttpPut(lua_State *state) - { - return CallHttpPostOrPut(state, HttpMethod_Put); - } -#endif - - -#if ORTHANC_ENABLE_CURL == 1 - int LuaContext::CallHttpDelete(lua_State *state) - { - LuaContext& that = GetLuaContext(state); - - // Check the types of the arguments - int nArgs = lua_gettop(state); - if (nArgs < 1 || nArgs > 2 || !lua_isstring(state, 1)) // URL - { - LOG(ERROR) << "Lua: Bad parameters to HttpDelete()"; - lua_pushnil(state); - return 1; - } - - // Configure the HTTP client class - const char* url = lua_tostring(state, 1); - that.httpClient_.SetMethod(HttpMethod_Delete); - that.httpClient_.SetUrl(url); - that.httpClient_.GetBody().clear(); - that.SetHttpHeaders(2); - - // Do the HTTP DELETE request - std::string s; - if (!that.httpClient_.Apply(s)) - { - LOG(ERROR) << "Lua: Error in HttpDelete() for URL " << url; - lua_pushnil(state); - } - else - { - lua_pushstring(state, "SUCCESS"); - } - - return 1; - } -#endif - - - void LuaContext::PushJson(const Json::Value& value) - { - if (value.isString()) - { - const std::string s = value.asString(); - lua_pushlstring(lua_, s.c_str(), s.size()); - } - else if (value.isDouble()) - { - lua_pushnumber(lua_, value.asDouble()); - } - else if (value.isInt()) - { - lua_pushinteger(lua_, value.asInt()); - } - else if (value.isUInt()) - { - lua_pushinteger(lua_, value.asUInt()); - } - else if (value.isBool()) - { - lua_pushboolean(lua_, value.asBool()); - } - else if (value.isNull()) - { - lua_pushnil(lua_); - } - else if (value.isArray()) - { - lua_newtable(lua_); - - // http://lua-users.org/wiki/SimpleLuaApiExample - for (Json::Value::ArrayIndex i = 0; i < value.size(); i++) - { - // Push the table index (note the "+1" because of Lua conventions) - lua_pushnumber(lua_, i + 1); - - // Push the value of the cell - PushJson(value[i]); - - // Stores the pair in the table - lua_rawset(lua_, -3); - } - } - else if (value.isObject()) - { - lua_newtable(lua_); - - Json::Value::Members members = value.getMemberNames(); - - for (Json::Value::Members::const_iterator - it = members.begin(); it != members.end(); ++it) - { - // Push the index of the cell - lua_pushlstring(lua_, it->c_str(), it->size()); - - // Push the value of the cell - PushJson(value[*it]); - - // Stores the pair in the table - lua_rawset(lua_, -3); - } - } - else - { - throw OrthancException(ErrorCode_JsonToLuaTable); - } - } - - - void LuaContext::GetJson(Json::Value& result, - lua_State* state, - int top, - bool keepStrings) - { - if (lua_istable(state, top)) - { - Json::Value tmp = Json::objectValue; - bool isArray = true; - size_t size = 0; - - // Code adapted from: http://stackoverflow.com/a/6142700/881731 - - // Push another reference to the table on top of the stack (so we know - // where it is, and this function can work for negative, positive and - // pseudo indices - lua_pushvalue(state, top); - // stack now contains: -1 => table - lua_pushnil(state); - // stack now contains: -1 => nil; -2 => table - while (lua_next(state, -2)) - { - // stack now contains: -1 => value; -2 => key; -3 => table - // copy the key so that lua_tostring does not modify the original - lua_pushvalue(state, -2); - // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table - std::string key(lua_tostring(state, -1)); - Json::Value v; - GetJson(v, state, -2, keepStrings); - - tmp[key] = v; - - size += 1; - try - { - if (!OnlyContainsDigits(key) || - boost::lexical_cast(key) != size) - { - isArray = false; - } - } - catch (boost::bad_lexical_cast&) - { - isArray = false; - } - - // pop value + copy of key, leaving original key - lua_pop(state, 2); - // stack now contains: -1 => key; -2 => table - } - // stack now contains: -1 => table (when lua_next returns 0 it pops the key - // but does not push anything.) - // Pop table - lua_pop(state, 1); - - // Stack is now the same as it was on entry to this function - - if (isArray) - { - result = Json::arrayValue; - for (size_t i = 0; i < size; i++) - { - result.append(tmp[boost::lexical_cast(i + 1)]); - } - } - else - { - result = tmp; - } - } - else if (lua_isnil(state, top)) - { - result = Json::nullValue; - } - else if (!keepStrings && - lua_isboolean(state, top)) - { - result = lua_toboolean(state, top) ? true : false; - } - else if (!keepStrings && - lua_isnumber(state, top)) - { - // Convert to "int" if truncation does not loose precision - double value = static_cast(lua_tonumber(state, top)); - int truncated = static_cast(value); - - if (std::abs(value - static_cast(truncated)) <= - std::numeric_limits::epsilon()) - { - result = truncated; - } - else - { - result = value; - } - } - else if (lua_isstring(state, top)) - { - // Caution: The "lua_isstring()" case must be the last, since - // Lua can convert most types to strings by default. - result = std::string(lua_tostring(state, top)); - } - else if (lua_isboolean(state, top)) - { - result = lua_toboolean(state, top) ? true : false; - } - else - { - LOG(WARNING) << "Unsupported Lua type when returning Json"; - result = Json::nullValue; - } - } - - - LuaContext::LuaContext() - { - lua_ = luaL_newstate(); - if (!lua_) - { - throw OrthancException(ErrorCode_CannotCreateLua); - } - - luaL_openlibs(lua_); - lua_register(lua_, "print", PrintToLog); - lua_register(lua_, "ParseJson", ParseJson); - lua_register(lua_, "DumpJson", DumpJson); - -#if ORTHANC_ENABLE_CURL == 1 - lua_register(lua_, "HttpGet", CallHttpGet); - lua_register(lua_, "HttpPost", CallHttpPost); - lua_register(lua_, "HttpPut", CallHttpPut); - lua_register(lua_, "HttpDelete", CallHttpDelete); - lua_register(lua_, "SetHttpCredentials", SetHttpCredentials); -#endif - - SetGlobalVariable("_LuaContext", this); - } - - - LuaContext::~LuaContext() - { - lua_close(lua_); - } - - - void LuaContext::ExecuteInternal(std::string* output, - const std::string& command) - { - log_.clear(); - int error = (luaL_loadbuffer(lua_, command.c_str(), command.size(), "line") || - lua_pcall(lua_, 0, 0, 0)); - - if (error) - { - assert(lua_gettop(lua_) >= 1); - - std::string description(lua_tostring(lua_, -1)); - lua_pop(lua_, 1); /* pop error message from the stack */ - throw OrthancException(ErrorCode_CannotExecuteLua, description); - } - - if (output != NULL) - { - *output = log_; - } - } - - - bool LuaContext::IsExistingFunction(const char* name) - { - lua_settop(lua_, 0); - lua_getglobal(lua_, name); - return lua_type(lua_, -1) == LUA_TFUNCTION; - } - - - void LuaContext::Execute(Json::Value& output, - const std::string& command) - { - std::string s; - ExecuteInternal(&s, command); - - Json::Reader reader; - if (!reader.parse(s, output)) - { - throw OrthancException(ErrorCode_BadJson); - } - } - - - void LuaContext::RegisterFunction(const char* name, - lua_CFunction func) - { - lua_register(lua_, name, func); - } - - - void LuaContext::SetGlobalVariable(const char* name, - void* value) - { - lua_pushlightuserdata(lua_, value); - lua_setglobal(lua_, name); - } - - - const void* LuaContext::GetGlobalVariable(lua_State* state, - const char* name) - { - lua_getglobal(state, name); - assert(lua_type(state, -1) == LUA_TLIGHTUSERDATA); - const void* value = lua_topointer(state, -1); - lua_pop(state, 1); - return value; - } - - - void LuaContext::GetDictionaryArgument(std::map& target, - lua_State* state, - int top, - bool keyToLowerCase) - { - target.clear(); - - if (lua_gettop(state) >= top) - { - Json::Value headers; - GetJson(headers, state, top, true); - - Json::Value::Members members = headers.getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - std::string key = members[i]; - - if (keyToLowerCase) - { - Toolbox::ToLowerCase(key); - } - - target[key] = headers[members[i]].asString(); - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Lua/LuaContext.h --- a/Core/Lua/LuaContext.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -// To have ORTHANC_ENABLE_LUA defined if using the shared library -#include "../OrthancFramework.h" - -#if !defined(ORTHANC_ENABLE_LUA) -# error The macro ORTHANC_ENABLE_LUA must be defined -#endif - -#if !defined(ORTHANC_ENABLE_CURL) -# error Macro ORTHANC_ENABLE_CURL must be defined -#endif - -#if ORTHANC_ENABLE_LUA == 0 -# error The Lua support is disabled, cannot include this file -#endif - -#if ORTHANC_ENABLE_CURL == 1 -# include "../HttpClient.h" -#endif - -extern "C" -{ -#include -} - -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC LuaContext : public boost::noncopyable - { - private: - friend class LuaFunctionCall; - - lua_State *lua_; - std::string log_; - -#if ORTHANC_ENABLE_CURL == 1 - HttpClient httpClient_; -#endif - - static int PrintToLog(lua_State *state); - static int ParseJson(lua_State *state); - static int DumpJson(lua_State *state); - -#if ORTHANC_ENABLE_CURL == 1 - static int SetHttpCredentials(lua_State *state); - static int CallHttpPostOrPut(lua_State *state, - HttpMethod method); - static int CallHttpGet(lua_State *state); - static int CallHttpPost(lua_State *state); - static int CallHttpPut(lua_State *state); - static int CallHttpDelete(lua_State *state); -#endif - - bool AnswerHttpQuery(lua_State* state); - - void ExecuteInternal(std::string* output, - const std::string& command); - - static void GetJson(Json::Value& result, - lua_State* state, - int top, - bool keepStrings); - - void SetHttpHeaders(int top); - - public: - LuaContext(); - - ~LuaContext(); - - void Execute(const std::string& command) - { - ExecuteInternal(NULL, command); - } - - void Execute(std::string& output, - const std::string& command) - { - ExecuteInternal(&output, command); - } - - void Execute(Json::Value& output, - const std::string& command); - - bool IsExistingFunction(const char* name); - -#if ORTHANC_ENABLE_CURL == 1 - void SetHttpCredentials(const char* username, - const char* password) - { - httpClient_.SetCredentials(username, password); - } -#endif - - void RegisterFunction(const char* name, - lua_CFunction func); - - void SetGlobalVariable(const char* name, - void* value); - - static LuaContext& GetLuaContext(lua_State *state); - - static const void* GetGlobalVariable(lua_State* state, - const char* name); - - void PushJson(const Json::Value& value); - - static void GetDictionaryArgument(std::map& target, - lua_State* state, - int top, - bool keyToLowerCase); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Lua/LuaFunctionCall.cpp --- a/Core/Lua/LuaFunctionCall.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,191 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "LuaFunctionCall.h" - -#include "../OrthancException.h" -#include "../Logging.h" - -#include -#include -#include - -namespace Orthanc -{ - void LuaFunctionCall::CheckAlreadyExecuted() - { - if (isExecuted_) - { - throw OrthancException(ErrorCode_LuaAlreadyExecuted); - } - } - - LuaFunctionCall::LuaFunctionCall(LuaContext& context, - const char* functionName) : - context_(context), - isExecuted_(false) - { - // Clear the stack to fulfill the invariant - lua_settop(context_.lua_, 0); - lua_getglobal(context_.lua_, functionName); - } - - void LuaFunctionCall::PushString(const std::string& value) - { - CheckAlreadyExecuted(); - lua_pushlstring(context_.lua_, value.c_str(), value.size()); - } - - void LuaFunctionCall::PushBoolean(bool value) - { - CheckAlreadyExecuted(); - lua_pushboolean(context_.lua_, value); - } - - void LuaFunctionCall::PushInteger(int value) - { - CheckAlreadyExecuted(); - lua_pushinteger(context_.lua_, value); - } - - void LuaFunctionCall::PushDouble(double value) - { - CheckAlreadyExecuted(); - lua_pushnumber(context_.lua_, value); - } - - void LuaFunctionCall::PushJson(const Json::Value& value) - { - CheckAlreadyExecuted(); - context_.PushJson(value); - } - - void LuaFunctionCall::ExecuteInternal(int numOutputs) - { - CheckAlreadyExecuted(); - - assert(lua_gettop(context_.lua_) >= 1); - int nargs = lua_gettop(context_.lua_) - 1; - int error = lua_pcall(context_.lua_, nargs, numOutputs, 0); - - if (error) - { - assert(lua_gettop(context_.lua_) >= 1); - - std::string description(lua_tostring(context_.lua_, -1)); - lua_pop(context_.lua_, 1); /* pop error message from the stack */ - - throw OrthancException(ErrorCode_CannotExecuteLua, description); - } - - if (lua_gettop(context_.lua_) < numOutputs) - { - throw OrthancException(ErrorCode_LuaBadOutput); - } - - isExecuted_ = true; - } - - bool LuaFunctionCall::ExecutePredicate() - { - ExecuteInternal(1); - - if (!lua_isboolean(context_.lua_, 1)) - { - throw OrthancException(ErrorCode_NotLuaPredicate); - } - - return lua_toboolean(context_.lua_, 1) != 0; - } - - - void LuaFunctionCall::ExecuteToJson(Json::Value& result, - bool keepStrings) - { - ExecuteInternal(1); - context_.GetJson(result, context_.lua_, lua_gettop(context_.lua_), keepStrings); - } - - - void LuaFunctionCall::ExecuteToString(std::string& result) - { - ExecuteInternal(1); - - int top = lua_gettop(context_.lua_); - if (lua_isstring(context_.lua_, top)) - { - result = lua_tostring(context_.lua_, top); - } - else - { - throw OrthancException(ErrorCode_LuaReturnsNoString); - } - } - - - void LuaFunctionCall::PushStringMap(const std::map& value) - { - Json::Value json = Json::objectValue; - - for (std::map::const_iterator - it = value.begin(); it != value.end(); ++it) - { - json[it->first] = it->second; - } - - PushJson(json); - } - - - void LuaFunctionCall::PushDicom(const DicomMap& dicom) - { - DicomArray a(dicom); - PushDicom(a); - } - - - void LuaFunctionCall::PushDicom(const DicomArray& dicom) - { - Json::Value value = Json::objectValue; - - for (size_t i = 0; i < dicom.GetSize(); i++) - { - const DicomValue& v = dicom.GetElement(i).GetValue(); - std::string s = (v.IsNull() || v.IsBinary()) ? "" : v.GetContent(); - value[dicom.GetElement(i).GetTag().Format()] = s; - } - - PushJson(value); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Lua/LuaFunctionCall.h --- a/Core/Lua/LuaFunctionCall.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "LuaContext.h" - -#include "../DicomFormat/DicomArray.h" -#include "../DicomFormat/DicomMap.h" - -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC LuaFunctionCall : public boost::noncopyable - { - private: - LuaContext& context_; - bool isExecuted_; - - void CheckAlreadyExecuted(); - - protected: - void ExecuteInternal(int numOutputs); - - lua_State* GetState() - { - return context_.lua_; - } - - public: - LuaFunctionCall(LuaContext& context, - const char* functionName); - - void PushString(const std::string& value); - - void PushBoolean(bool value); - - void PushInteger(int value); - - void PushDouble(double value); - - void PushJson(const Json::Value& value); - - void PushStringMap(const std::map& value); - - void PushDicom(const DicomMap& dicom); - - void PushDicom(const DicomArray& dicom); - - void Execute() - { - ExecuteInternal(0); - } - - bool ExecutePredicate(); - - void ExecuteToJson(Json::Value& result, - bool keepStrings); - - void ExecuteToString(std::string& result); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/MetricsRegistry.cpp --- a/Core/MetricsRegistry.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,330 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "MetricsRegistry.h" - -#include "ChunkedBuffer.h" -#include "Compatibility.h" -#include "OrthancException.h" - -namespace Orthanc -{ - static const boost::posix_time::ptime GetNow() - { - return boost::posix_time::microsec_clock::universal_time(); - } - - class MetricsRegistry::Item - { - private: - MetricsType type_; - boost::posix_time::ptime time_; - bool hasValue_; - float value_; - - void Touch(float value, - const boost::posix_time::ptime& now) - { - hasValue_ = true; - value_ = value; - time_ = now; - } - - void Touch(float value) - { - Touch(value, GetNow()); - } - - void UpdateMax(float value, - int duration) - { - if (hasValue_) - { - const boost::posix_time::ptime now = GetNow(); - - if (value > value_ || - (now - time_).total_seconds() > duration) - { - Touch(value, now); - } - } - else - { - Touch(value); - } - } - - void UpdateMin(float value, - int duration) - { - if (hasValue_) - { - const boost::posix_time::ptime now = GetNow(); - - if (value < value_ || - (now - time_).total_seconds() > duration) - { - Touch(value, now); - } - } - else - { - Touch(value); - } - } - - public: - Item(MetricsType type) : - type_(type), - hasValue_(false) - { - } - - MetricsType GetType() const - { - return type_; - } - - void Update(float value) - { - switch (type_) - { - case MetricsType_Default: - Touch(value); - break; - - case MetricsType_MaxOver10Seconds: - UpdateMax(value, 10); - break; - - case MetricsType_MaxOver1Minute: - UpdateMax(value, 60); - break; - - case MetricsType_MinOver10Seconds: - UpdateMin(value, 10); - break; - - case MetricsType_MinOver1Minute: - UpdateMin(value, 60); - break; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - bool HasValue() const - { - return hasValue_; - } - - const boost::posix_time::ptime& GetTime() const - { - if (hasValue_) - { - return time_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - float GetValue() const - { - if (hasValue_) - { - return value_; - } - else - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - }; - - - MetricsRegistry::~MetricsRegistry() - { - for (Content::iterator it = content_.begin(); it != content_.end(); ++it) - { - assert(it->second != NULL); - delete it->second; - } - } - - - void MetricsRegistry::SetEnabled(bool enabled) - { - boost::mutex::scoped_lock lock(mutex_); - enabled_ = enabled; - } - - - void MetricsRegistry::Register(const std::string& name, - MetricsType type) - { - boost::mutex::scoped_lock lock(mutex_); - - Content::iterator found = content_.find(name); - - if (found == content_.end()) - { - content_[name] = new Item(type); - } - else - { - assert(found->second != NULL); - - // This metrics already exists: Only recreate it if there is a - // mismatch in the type of metrics - if (found->second->GetType() != type) - { - delete found->second; - found->second = new Item(type); - } - } - } - - - void MetricsRegistry::SetValueInternal(const std::string& name, - float value, - MetricsType type) - { - boost::mutex::scoped_lock lock(mutex_); - - Content::iterator found = content_.find(name); - - if (found == content_.end()) - { - std::unique_ptr item(new Item(type)); - item->Update(value); - content_[name] = item.release(); - } - else - { - assert(found->second != NULL); - found->second->Update(value); - } - } - - - MetricsType MetricsRegistry::GetMetricsType(const std::string& name) - { - boost::mutex::scoped_lock lock(mutex_); - - Content::const_iterator found = content_.find(name); - - if (found == content_.end()) - { - throw OrthancException(ErrorCode_InexistentItem); - } - else - { - assert(found->second != NULL); - return found->second->GetType(); - } - } - - - void MetricsRegistry::ExportPrometheusText(std::string& s) - { - // https://www.boost.org/doc/libs/1_69_0/doc/html/date_time/examples.html#date_time.examples.seconds_since_epoch - static const boost::posix_time::ptime EPOCH(boost::gregorian::date(1970, 1, 1)); - - boost::mutex::scoped_lock lock(mutex_); - - s.clear(); - - if (!enabled_) - { - return; - } - - ChunkedBuffer buffer; - - for (Content::const_iterator it = content_.begin(); - it != content_.end(); ++it) - { - assert(it->second != NULL); - - if (it->second->HasValue()) - { - boost::posix_time::time_duration diff = it->second->GetTime() - EPOCH; - - std::string line = (it->first + " " + - boost::lexical_cast(it->second->GetValue()) + " " + - boost::lexical_cast(diff.total_milliseconds()) + "\n"); - - buffer.AddChunk(line); - } - } - - buffer.Flatten(s); - } - - - void MetricsRegistry::SharedMetrics::Add(float delta) - { - boost::mutex::scoped_lock lock(mutex_); - value_ += delta; - registry_.SetValue(name_, value_); - } - - - void MetricsRegistry::Timer::Start() - { - if (registry_.IsEnabled()) - { - active_ = true; - start_ = GetNow(); - } - else - { - active_ = false; - } - } - - - MetricsRegistry::Timer::~Timer() - { - if (active_) - { - boost::posix_time::time_duration diff = GetNow() - start_; - registry_.SetValue( - name_, static_cast(diff.total_milliseconds()), type_); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/MetricsRegistry.h --- a/Core/MetricsRegistry.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,191 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "OrthancFramework.h" - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The class MetricsRegistry cannot be used in sandboxed environments -#endif - -#include -#include - -namespace Orthanc -{ - enum MetricsType - { - MetricsType_Default, - MetricsType_MaxOver10Seconds, - MetricsType_MaxOver1Minute, - MetricsType_MinOver10Seconds, - MetricsType_MinOver1Minute - }; - - class ORTHANC_PUBLIC MetricsRegistry : public boost::noncopyable - { - private: - class Item; - - typedef std::map Content; - - bool enabled_; - boost::mutex mutex_; - Content content_; - - void SetValueInternal(const std::string& name, - float value, - MetricsType type); - - public: - MetricsRegistry() : - enabled_(true) - { - } - - ~MetricsRegistry(); - - bool IsEnabled() const - { - return enabled_; - } - - void SetEnabled(bool enabled); - - void Register(const std::string& name, - MetricsType type); - - void SetValue(const std::string& name, - float value, - MetricsType type) - { - // Inlining to avoid loosing time if metrics are disabled - if (enabled_) - { - SetValueInternal(name, value, type); - } - } - - void SetValue(const std::string& name, - float value) - { - SetValue(name, value, MetricsType_Default); - } - - MetricsType GetMetricsType(const std::string& name); - - // https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format - void ExportPrometheusText(std::string& s); - - - class ORTHANC_PUBLIC SharedMetrics : public boost::noncopyable - { - private: - boost::mutex mutex_; - MetricsRegistry& registry_; - std::string name_; - float value_; - - public: - SharedMetrics(MetricsRegistry& registry, - const std::string& name, - MetricsType type) : - registry_(registry), - name_(name), - value_(0) - { - } - - void Add(float delta); - }; - - - class ORTHANC_PUBLIC ActiveCounter : public boost::noncopyable - { - private: - SharedMetrics& metrics_; - - public: - ActiveCounter(SharedMetrics& metrics) : - metrics_(metrics) - { - metrics_.Add(1); - } - - ~ActiveCounter() - { - metrics_.Add(-1); - } - }; - - - class ORTHANC_PUBLIC Timer : public boost::noncopyable - { - private: - MetricsRegistry& registry_; - std::string name_; - MetricsType type_; - bool active_; - boost::posix_time::ptime start_; - - void Start(); - - public: - Timer(MetricsRegistry& registry, - const std::string& name) : - registry_(registry), - name_(name), - type_(MetricsType_MaxOver10Seconds) - { - Start(); - } - - Timer(MetricsRegistry& registry, - const std::string& name, - MetricsType type) : - registry_(registry), - name_(name), - type_(type) - { - Start(); - } - - ~Timer(); - }; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/MultiThreading/IRunnableBySteps.h --- a/Core/MultiThreading/IRunnableBySteps.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../IDynamicObject.h" - -namespace Orthanc -{ - class IRunnableBySteps : public IDynamicObject - { - public: - virtual ~IRunnableBySteps() - { - } - - // Must return "true" if the runnable wishes to continue. Must - // return "false" if the runnable has not finished its job. - virtual bool Step() = 0; - - static void RunUntilDone(IRunnableBySteps& runnable) - { - while (runnable.Step()); - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/MultiThreading/RunnableWorkersPool.cpp --- a/Core/MultiThreading/RunnableWorkersPool.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,171 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "RunnableWorkersPool.h" - -#include "SharedMessageQueue.h" -#include "../Compatibility.h" -#include "../OrthancException.h" -#include "../Logging.h" - -namespace Orthanc -{ - struct RunnableWorkersPool::PImpl - { - class Worker - { - private: - const bool& continue_; - SharedMessageQueue& queue_; - boost::thread thread_; - - static void WorkerThread(Worker* that) - { - while (that->continue_) - { - try - { - std::unique_ptr obj(that->queue_.Dequeue(100)); - if (obj.get() != NULL) - { - IRunnableBySteps& runnable = *dynamic_cast(obj.get()); - - bool wishToContinue = runnable.Step(); - - if (wishToContinue) - { - // The runnable wishes to continue, reinsert it at the beginning of the queue - that->queue_.Enqueue(obj.release()); - } - } - } - catch (OrthancException& e) - { - LOG(ERROR) << "Exception while handling some runnable object: " << e.What(); - } - catch (std::bad_alloc&) - { - LOG(ERROR) << "Not enough memory to handle some runnable object"; - } - catch (std::exception& e) - { - LOG(ERROR) << "std::exception while handling some runnable object: " << e.what(); - } - catch (...) - { - LOG(ERROR) << "Native exception while handling some runnable object"; - } - } - } - - public: - Worker(const bool& globalContinue, - SharedMessageQueue& queue) : - continue_(globalContinue), - queue_(queue) - { - thread_ = boost::thread(WorkerThread, this); - } - - void Join() - { - if (thread_.joinable()) - { - thread_.join(); - } - } - }; - - - bool continue_; - std::vector workers_; - SharedMessageQueue queue_; - }; - - - - RunnableWorkersPool::RunnableWorkersPool(size_t countWorkers) : pimpl_(new PImpl) - { - pimpl_->continue_ = true; - - if (countWorkers == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - pimpl_->workers_.resize(countWorkers); - - for (size_t i = 0; i < countWorkers; i++) - { - pimpl_->workers_[i] = new PImpl::Worker(pimpl_->continue_, pimpl_->queue_); - } - } - - - void RunnableWorkersPool::Stop() - { - if (pimpl_->continue_) - { - pimpl_->continue_ = false; - - for (size_t i = 0; i < pimpl_->workers_.size(); i++) - { - PImpl::Worker* worker = pimpl_->workers_[i]; - - if (worker != NULL) - { - worker->Join(); - delete worker; - } - } - } - } - - - RunnableWorkersPool::~RunnableWorkersPool() - { - Stop(); - } - - - void RunnableWorkersPool::Add(IRunnableBySteps* runnable) - { - if (!pimpl_->continue_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - - pimpl_->queue_.Enqueue(runnable); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/MultiThreading/RunnableWorkersPool.h --- a/Core/MultiThreading/RunnableWorkersPool.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "IRunnableBySteps.h" - -#include - -namespace Orthanc -{ - class RunnableWorkersPool : public boost::noncopyable - { - private: - struct PImpl; - boost::shared_ptr pimpl_; - - void Stop(); - - public: - explicit RunnableWorkersPool(size_t countWorkers); - - ~RunnableWorkersPool(); - - void Add(IRunnableBySteps* runnable); // Takes the ownership - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/MultiThreading/Semaphore.cpp --- a/Core/MultiThreading/Semaphore.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "Semaphore.h" - -#include "../OrthancException.h" - - -namespace Orthanc -{ - Semaphore::Semaphore(unsigned int availableResources) : - availableResources_(availableResources) - { - if (availableResources_ == 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - void Semaphore::Release() - { - boost::mutex::scoped_lock lock(mutex_); - - availableResources_++; - condition_.notify_one(); - } - - void Semaphore::Acquire() - { - boost::mutex::scoped_lock lock(mutex_); - - while (availableResources_ == 0) - { - condition_.wait(lock); - } - - availableResources_--; - } - - bool Semaphore::TryAcquire() - { - boost::mutex::scoped_lock lock(mutex_); - - if (availableResources_ == 0) - { - return false; - } - - availableResources_--; - return true; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/MultiThreading/Semaphore.h --- a/Core/MultiThreading/Semaphore.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include -#include - -namespace Orthanc -{ - class Semaphore : public boost::noncopyable - { - private: - unsigned int availableResources_; - boost::mutex mutex_; - boost::condition_variable condition_; - - void Release(); - - void Acquire(); - - bool TryAcquire(); - public: - explicit Semaphore(unsigned int availableResources); - - unsigned int GetAvailableResourcesCount() const - { - return availableResources_; - } - - - class Locker : public boost::noncopyable - { - private: - Semaphore& that_; - - public: - explicit Locker(Semaphore& that) : - that_(that) - { - that_.Acquire(); - } - - ~Locker() - { - that_.Release(); - } - }; - - class TryLocker : public boost::noncopyable - { - private: - Semaphore& that_; - bool isAcquired_; - - public: - explicit TryLocker(Semaphore& that) : - that_(that) - { - isAcquired_ = that_.TryAcquire(); - } - - ~TryLocker() - { - if (isAcquired_) - { - that_.Release(); - } - } - - bool IsAcquired() const - { - return isAcquired_; - } - }; - - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/MultiThreading/SharedMessageQueue.cpp --- a/Core/MultiThreading/SharedMessageQueue.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,211 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "SharedMessageQueue.h" - - -#include "../Compatibility.h" - - -/** - * FIFO (queue): - * - * back front - * +--+--+--+--+--+--+--+--+--+--+--+ - * Enqueue -> | | | | | | | | | | | | - * | | | | | | | | | | | | -> Dequeue - * +--+--+--+--+--+--+--+--+--+--+--+ - * ^ - * | - * Make room here - * - * - * LIFO (stack): - * - * back front - * +--+--+--+--+--+--+--+--+--+--+--+ - * | | | | | | | | | | | | <- Enqueue - * | | | | | | | | | | | | -> Dequeue - * +--+--+--+--+--+--+--+--+--+--+--+ - * ^ - * | - * Make room here - **/ - - -namespace Orthanc -{ - SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) : - isFifo_(true), - maxSize_(maxSize) - { - } - - - SharedMessageQueue::~SharedMessageQueue() - { - for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it) - { - delete *it; - } - } - - - void SharedMessageQueue::Enqueue(IDynamicObject* message) - { - boost::mutex::scoped_lock lock(mutex_); - - if (maxSize_ != 0 && queue_.size() > maxSize_) - { - if (isFifo_) - { - // Too many elements in the queue: Make room - delete queue_.front(); - queue_.pop_front(); - } - else - { - // Too many elements in the stack: Make room - delete queue_.back(); - queue_.pop_back(); - } - } - - if (isFifo_) - { - // Queue policy (FIFO) - queue_.push_back(message); - } - else - { - // Stack policy (LIFO) - queue_.push_front(message); - } - - elementAvailable_.notify_one(); - } - - - IDynamicObject* SharedMessageQueue::Dequeue(int32_t millisecondsTimeout) - { - boost::mutex::scoped_lock lock(mutex_); - - // Wait for a message to arrive in the FIFO queue - while (queue_.empty()) - { - if (millisecondsTimeout == 0) - { - elementAvailable_.wait(lock); - } - else - { - bool success = elementAvailable_.timed_wait - (lock, boost::posix_time::milliseconds(millisecondsTimeout)); - if (!success) - { - return NULL; - } - } - } - - std::unique_ptr message(queue_.front()); - queue_.pop_front(); - - if (queue_.empty()) - { - emptied_.notify_all(); - } - - return message.release(); - } - - - - bool SharedMessageQueue::WaitEmpty(int32_t millisecondsTimeout) - { - boost::mutex::scoped_lock lock(mutex_); - - // Wait for the queue to become empty - while (!queue_.empty()) - { - if (millisecondsTimeout == 0) - { - emptied_.wait(lock); - } - else - { - if (!emptied_.timed_wait - (lock, boost::posix_time::milliseconds(millisecondsTimeout))) - { - return false; - } - } - } - - return true; - } - - - void SharedMessageQueue::SetFifoPolicy() - { - boost::mutex::scoped_lock lock(mutex_); - isFifo_ = true; - } - - void SharedMessageQueue::SetLifoPolicy() - { - boost::mutex::scoped_lock lock(mutex_); - isFifo_ = false; - } - - void SharedMessageQueue::Clear() - { - boost::mutex::scoped_lock lock(mutex_); - - if (queue_.empty()) - { - return; - } - else - { - while (!queue_.empty()) - { - std::unique_ptr message(queue_.front()); - queue_.pop_front(); - } - - emptied_.notify_all(); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/MultiThreading/SharedMessageQueue.h --- a/Core/MultiThreading/SharedMessageQueue.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../IDynamicObject.h" - -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC SharedMessageQueue : public boost::noncopyable - { - private: - typedef std::list Queue; - - bool isFifo_; - unsigned int maxSize_; - Queue queue_; - boost::mutex mutex_; - boost::condition_variable elementAvailable_; - boost::condition_variable emptied_; - - public: - explicit SharedMessageQueue(unsigned int maxSize = 0); - - ~SharedMessageQueue(); - - // This transfers the ownership of the message - void Enqueue(IDynamicObject* message); - - // The caller is responsible to delete the dequeud message! - IDynamicObject* Dequeue(int32_t millisecondsTimeout); - - bool WaitEmpty(int32_t millisecondsTimeout); - - bool IsFifoPolicy() const - { - return isFifo_; - } - - bool IsLifoPolicy() const - { - return !isFifo_; - } - - void SetFifoPolicy(); - - void SetLifoPolicy(); - - void Clear(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/OrthancException.h --- a/Core/OrthancException.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "Compatibility.h" -#include "Enumerations.h" -#include "Logging.h" -#include "OrthancFramework.h" - -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC OrthancException - { - private: - OrthancException(); // Forbidden - - OrthancException& operator= (const OrthancException&); // Forbidden - - ErrorCode errorCode_; - HttpStatus httpStatus_; - - // New in Orthanc 1.5.0 - std::unique_ptr details_; - - public: - OrthancException(const OrthancException& other) : - errorCode_(other.errorCode_), - httpStatus_(other.httpStatus_) - { - if (other.details_.get() != NULL) - { - details_.reset(new std::string(*other.details_)); - } - } - - explicit OrthancException(ErrorCode errorCode) : - errorCode_(errorCode), - httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)) - { - } - - OrthancException(ErrorCode errorCode, - const std::string& details, - bool log = true) : - errorCode_(errorCode), - httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)), - details_(new std::string(details)) - { -#if ORTHANC_ENABLE_LOGGING == 1 - if (log) - { - LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details; - } -#endif - } - - OrthancException(ErrorCode errorCode, - HttpStatus httpStatus) : - errorCode_(errorCode), - httpStatus_(httpStatus) - { - } - - OrthancException(ErrorCode errorCode, - HttpStatus httpStatus, - const std::string& details, - bool log = true) : - errorCode_(errorCode), - httpStatus_(httpStatus), - details_(new std::string(details)) - { -#if ORTHANC_ENABLE_LOGGING == 1 - if (log) - { - LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details; - } -#endif - } - - ErrorCode GetErrorCode() const - { - return errorCode_; - } - - HttpStatus GetHttpStatus() const - { - return httpStatus_; - } - - const char* What() const - { - return EnumerationToString(errorCode_); - } - - bool HasDetails() const - { - return details_.get() != NULL; - } - - const char* GetDetails() const - { - if (details_.get() == NULL) - { - return ""; - } - else - { - return details_->c_str(); - } - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/OrthancFramework.cpp --- a/Core/OrthancFramework.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "OrthancFramework.h" - -#if !defined(ORTHANC_ENABLE_CURL) -# error Macro ORTHANC_ENABLE_CURL must be defined -#endif - -#if !defined(ORTHANC_ENABLE_SSL) -# error Macro ORTHANC_ENABLE_SSL must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK) -# error Macro ORTHANC_ENABLE_DCMTK must be defined -#endif - -#if !defined(ORTHANC_ENABLE_DCMTK_NETWORKING) -# error Macro ORTHANC_ENABLE_DCMTK_NETWORKING must be defined -#endif - -#if ORTHANC_ENABLE_CURL == 1 -# include "HttpClient.h" -#endif - -#if ORTHANC_ENABLE_DCMTK == 1 -# include "DicomParsing/FromDcmtkBridge.h" -# if ORTHANC_ENABLE_DCMTK_NETWORKING == 1 -# include -# endif -#endif - -#include "Logging.h" -#include "Toolbox.h" - - -namespace Orthanc -{ - void InitializeFramework(const std::string& locale, - bool loadPrivateDictionary) - { - Logging::Initialize(); - -#if (ORTHANC_ENABLE_LOCALE == 1) && !defined(__EMSCRIPTEN__) // No global locale in wasm/asm.js - if (locale.empty()) - { - Toolbox::InitializeGlobalLocale(NULL); - } - else - { - Toolbox::InitializeGlobalLocale(locale.c_str()); - } -#endif - - Toolbox::InitializeOpenSsl(); - -#if ORTHANC_ENABLE_CURL == 1 - HttpClient::GlobalInitialize(); -#endif - -#if ORTHANC_ENABLE_DCMTK == 1 - FromDcmtkBridge::InitializeDictionary(true); - FromDcmtkBridge::InitializeCodecs(); -#endif - -#if (ORTHANC_ENABLE_DCMTK == 1 && \ - ORTHANC_ENABLE_DCMTK_NETWORKING == 1) - /* Disable "gethostbyaddr" (which results in memory leaks) and use raw IP addresses */ - dcmDisableGethostbyaddr.set(OFTrue); -#endif - } - - - void FinalizeFramework() - { -#if ORTHANC_ENABLE_DCMTK == 1 - FromDcmtkBridge::FinalizeCodecs(); -#endif - -#if ORTHANC_ENABLE_CURL == 1 - HttpClient::GlobalFinalize(); -#endif - - Toolbox::FinalizeOpenSsl(); - -#if (ORTHANC_ENABLE_LOCALE == 1) && !defined(__EMSCRIPTEN__) - Toolbox::FinalizeGlobalLocale(); -#endif - - Logging::Finalize(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/OrthancFramework.h --- a/Core/OrthancFramework.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -/** - * Besides the "pragma once" above that only protects this file, - * define a macro to prevent including different versions of - * "OrthancFramework.h" - **/ -#ifndef __ORTHANC_FRAMEWORK_H -#define __ORTHANC_FRAMEWORK_H - -#if !defined(ORTHANC_BUILDING_FRAMEWORK_LIBRARY) -# error The macro ORTHANC_BUILDING_FRAMEWORK_LIBRARY must be defined -#endif - -/** - * It is implied that if this file is used, we're building the Orthanc - * framework (not using it as a shared library): We don't use the - * common "BUILDING_DLL" - * construction. https://gcc.gnu.org/wiki/Visibility - **/ -#if ORTHANC_BUILDING_FRAMEWORK_LIBRARY == 1 -# if defined(_WIN32) || defined (__CYGWIN__) -# define ORTHANC_PUBLIC __declspec(dllexport) -# define ORTHANC_LOCAL -# else -# if __GNUC__ >= 4 -# define ORTHANC_PUBLIC __attribute__((visibility ("default"))) -# define ORTHANC_LOCAL __attribute__((visibility ("hidden"))) -# else -# define ORTHANC_PUBLIC -# define ORTHANC_LOCAL -# pragma warning Unknown dynamic link import/export semantics -# endif -# endif -#else -# define ORTHANC_PUBLIC -# define ORTHANC_LOCAL -#endif - - -#include - -namespace Orthanc -{ - ORTHANC_PUBLIC void InitializeFramework(const std::string& locale, - bool loadPrivateDictionary); - - ORTHANC_PUBLIC void FinalizeFramework(); -} - - -#endif /* __ORTHANC_FRAMEWORK_H */ diff -r 6c6239aec462 -r d25f4c0fa160 Core/Pkcs11.cpp --- a/Core/Pkcs11.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,311 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "Pkcs11.h" - - -#if defined(OPENSSL_NO_RSA) || defined(OPENSSL_NO_EC) || defined(OPENSSL_NO_ECDSA) || defined(OPENSSL_NO_ECDH) -# error OpenSSL was compiled without support for RSA, EC, ECDSA or ECDH -#endif - - -#include "Logging.h" -#include "OrthancException.h" -#include "SystemToolbox.h" - -extern "C" -{ -# include // This is P11's "engine.h" -# include -} - -#include - - -namespace Orthanc -{ - namespace Pkcs11 - { - static const char* PKCS11_ENGINE_ID = "pkcs11"; - static const char* PKCS11_ENGINE_NAME = "PKCS#11 for Orthanc"; - static const ENGINE_CMD_DEFN PKCS11_ENGINE_COMMANDS[] = - { - { - CMD_MODULE_PATH, - "MODULE_PATH", - "Specifies the path to the PKCS#11 module shared library", - ENGINE_CMD_FLAG_STRING - }, - { - CMD_PIN, - "PIN", - "Specifies the pin code", - ENGINE_CMD_FLAG_STRING - }, - { - CMD_VERBOSE, - "VERBOSE", - "Print additional details", - ENGINE_CMD_FLAG_NO_INPUT - }, - { - CMD_LOAD_CERT_CTRL, - "LOAD_CERT_CTRL", - "Get the certificate from card", - ENGINE_CMD_FLAG_INTERNAL - }, - { - 0, - NULL, - NULL, - 0 - } - }; - - - static bool pkcs11Initialized_ = false; - static ENGINE_CTX *context_ = NULL; - - static int EngineInitialize(ENGINE* engine) - { - if (context_ == NULL) - { - return 0; - } - else - { - return pkcs11_init(context_); - } - } - - - static int EngineFinalize(ENGINE* engine) - { - if (context_ == NULL) - { - return 0; - } - else - { - return pkcs11_finish(context_); - } - } - - - static int EngineDestroy(ENGINE* engine) - { - return (context_ == NULL ? 0 : 1); - } - - - static int EngineControl(ENGINE *engine, - int command, - long i, - void *p, - void (*f) ()) - { - if (context_ == NULL) - { - return 0; - } - else - { - return pkcs11_engine_ctrl(context_, command, i, p, f); - } - } - - - static EVP_PKEY *EngineLoadPublicKey(ENGINE *engine, - const char *s_key_id, - UI_METHOD *ui_method, - void *callback_data) - { - if (context_ == NULL) - { - return 0; - } - else - { - return pkcs11_load_public_key(context_, s_key_id, ui_method, callback_data); - } - } - - - static EVP_PKEY *EngineLoadPrivateKey(ENGINE *engine, - const char *s_key_id, - UI_METHOD *ui_method, - void *callback_data) - { - if (context_ == NULL) - { - return 0; - } - else - { - return pkcs11_load_private_key(context_, s_key_id, ui_method, callback_data); - } - } - - - static ENGINE* LoadEngine() - { - // This function creates an engine for PKCS#11 and inspired by - // the "ENGINE_load_dynamic" function from OpenSSL, in file - // "crypto/engine/eng_dyn.c" - - ENGINE* engine = ENGINE_new(); - if (!engine) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot create an OpenSSL engine for PKCS#11"); - } - - // Create a PKCS#11 context using libp11 - context_ = pkcs11_new(); - if (!context_) - { - ENGINE_free(engine); - throw OrthancException(ErrorCode_InternalError, - "Cannot create a libp11 context for PKCS#11"); - } - - if (!ENGINE_set_id(engine, PKCS11_ENGINE_ID) || - !ENGINE_set_name(engine, PKCS11_ENGINE_NAME) || - !ENGINE_set_cmd_defns(engine, PKCS11_ENGINE_COMMANDS) || - - // Register the callback functions - !ENGINE_set_init_function(engine, EngineInitialize) || - !ENGINE_set_finish_function(engine, EngineFinalize) || - !ENGINE_set_destroy_function(engine, EngineDestroy) || - !ENGINE_set_ctrl_function(engine, EngineControl) || - !ENGINE_set_load_pubkey_function(engine, EngineLoadPublicKey) || - !ENGINE_set_load_privkey_function(engine, EngineLoadPrivateKey) || - - !ENGINE_set_RSA(engine, PKCS11_get_rsa_method()) || - -#if OPENSSL_VERSION_NUMBER < 0x10100000L // OpenSSL 1.0.2 - !ENGINE_set_ECDSA(engine, PKCS11_get_ecdsa_method()) || - !ENGINE_set_ECDH(engine, PKCS11_get_ecdh_method()) || -#else - !ENGINE_set_EC(engine, PKCS11_get_ec_key_method()) || -#endif - - // Make OpenSSL know about our PKCS#11 engine - !ENGINE_add(engine)) - { - pkcs11_finish(context_); - ENGINE_free(engine); - throw OrthancException(ErrorCode_InternalError, - "Cannot initialize the OpenSSL engine for PKCS#11"); - } - - // If the "ENGINE_add" worked, it gets a structural - // reference. We release our just-created reference. - ENGINE_free(engine); - - return ENGINE_by_id(PKCS11_ENGINE_ID); - } - - - bool IsInitialized() - { - return pkcs11Initialized_; - } - - const char* GetEngineIdentifier() - { - return PKCS11_ENGINE_ID; - } - - void Initialize(const std::string& module, - const std::string& pin, - bool verbose) - { - if (pkcs11Initialized_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "The PKCS#11 engine has already been initialized"); - } - - if (module.empty() || - !SystemToolbox::IsRegularFile(module)) - { - throw OrthancException( - ErrorCode_InexistentFile, - "The PKCS#11 module must be a path to one shared library (DLL or .so)"); - } - - ENGINE* engine = LoadEngine(); - if (!engine) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot create an OpenSSL engine for PKCS#11"); - } - - if (!ENGINE_ctrl_cmd_string(engine, "MODULE_PATH", module.c_str(), 0)) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot configure the OpenSSL dynamic engine for PKCS#11"); - } - - if (verbose) - { - ENGINE_ctrl_cmd_string(engine, "VERBOSE", NULL, 0); - } - - if (!pin.empty() && - !ENGINE_ctrl_cmd_string(engine, "PIN", pin.c_str(), 0)) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot set the PIN code for PKCS#11"); - } - - if (!ENGINE_init(engine)) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot initialize the OpenSSL dynamic engine for PKCS#11"); - } - - LOG(WARNING) << "The PKCS#11 engine has been successfully initialized"; - pkcs11Initialized_ = true; - } - - - void Finalize() - { - // Nothing to do, the unregistration of the engine is - // automatically done by OpenSSL - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Pkcs11.h --- a/Core/Pkcs11.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if !defined(ORTHANC_ENABLE_PKCS11) -# error The macro ORTHANC_ENABLE_PKCS11 must be defined -#endif - -#if !defined(ORTHANC_ENABLE_SSL) -# error The macro ORTHANC_ENABLE_SSL must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error This file cannot be used in sandboxed environments -#endif - -#if ORTHANC_ENABLE_PKCS11 != 1 || ORTHANC_ENABLE_SSL != 1 -# error This file cannot be used if OpenSSL or PKCS#11 support is disabled -#endif - - -#include - -namespace Orthanc -{ - namespace Pkcs11 - { - const char* GetEngineIdentifier(); - - bool IsInitialized(); - - void Initialize(const std::string& module, - const std::string& pin, - bool verbose); - - void Finalize(); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/PrecompiledHeaders.cpp --- a/Core/PrecompiledHeaders.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" diff -r 6c6239aec462 -r d25f4c0fa160 Core/PrecompiledHeaders.h --- a/Core/PrecompiledHeaders.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if defined(_WIN32) && !defined(NOMINMAX) -#define NOMINMAX -#endif - -#if ORTHANC_USE_PRECOMPILED_HEADERS == 1 - -#include "OrthancFramework.h" // Must be the first one - -//#include -//#include -#include -//#include -//#include -#include -#include - -#include - -#if ORTHANC_ENABLE_PUGIXML == 1 -# include -#endif - -#include "Compatibility.h" -#include "Enumerations.h" -#include "Logging.h" -#include "OrthancException.h" -#include "OrthancFramework.h" -#include "Toolbox.h" - -#if ORTHANC_ENABLE_DCMTK == 1 -// Headers from DCMTK used in Orthanc headers -# include -# include -# include -# include -#endif - -#if ORTHANC_ENABLE_DCMTK_NETWORKING == 1 -# include "DicomNetworking/DicomServer.h" - -// Headers from DCMTK used in Orthanc headers -# include -#endif - -#endif diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApi.cpp --- a/Core/RestApi/RestApi.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,278 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "RestApi.h" - -#include "../Logging.h" - -#include // To define "_exit()" under Windows -#include - -namespace Orthanc -{ - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - class HttpHandlerVisitor : public RestApiHierarchy::IVisitor - { - private: - RestApi& api_; - RestApiOutput& output_; - RequestOrigin origin_; - const char* remoteIp_; - const char* username_; - HttpMethod method_; - const IHttpHandler::Arguments& headers_; - const IHttpHandler::Arguments& getArguments_; - const void* bodyData_; - size_t bodySize_; - - public: - HttpHandlerVisitor(RestApi& api, - RestApiOutput& output, - RequestOrigin origin, - const char* remoteIp, - const char* username, - HttpMethod method, - const IHttpHandler::Arguments& headers, - const IHttpHandler::Arguments& getArguments, - const void* bodyData, - size_t bodySize) : - api_(api), - output_(output), - origin_(origin), - remoteIp_(remoteIp), - username_(username), - method_(method), - headers_(headers), - getArguments_(getArguments), - bodyData_(bodyData), - bodySize_(bodySize) - { - } - - virtual bool Visit(const RestApiHierarchy::Resource& resource, - const UriComponents& uri, - const IHttpHandler::Arguments& components, - const UriComponents& trailing) - { - if (resource.HasHandler(method_)) - { - switch (method_) - { - case HttpMethod_Get: - { - RestApiGetCall call(output_, api_, origin_, remoteIp_, username_, - headers_, components, trailing, uri, getArguments_); - resource.Handle(call); - return true; - } - - case HttpMethod_Post: - { - RestApiPostCall call(output_, api_, origin_, remoteIp_, username_, - headers_, components, trailing, uri, bodyData_, bodySize_); - resource.Handle(call); - return true; - } - - case HttpMethod_Delete: - { - RestApiDeleteCall call(output_, api_, origin_, remoteIp_, username_, - headers_, components, trailing, uri); - resource.Handle(call); - return true; - } - - case HttpMethod_Put: - { - RestApiPutCall call(output_, api_, origin_, remoteIp_, username_, - headers_, components, trailing, uri, bodyData_, bodySize_); - resource.Handle(call); - return true; - } - - default: - return false; - } - } - - return false; - } - }; - } - - - - static void AddMethod(std::string& target, - const std::string& method) - { - if (target.size() > 0) - target += "," + method; - else - target = method; - } - - static std::string MethodsToString(const std::set& methods) - { - std::string s; - - if (methods.find(HttpMethod_Get) != methods.end()) - { - AddMethod(s, "GET"); - } - - if (methods.find(HttpMethod_Post) != methods.end()) - { - AddMethod(s, "POST"); - } - - if (methods.find(HttpMethod_Put) != methods.end()) - { - AddMethod(s, "PUT"); - } - - if (methods.find(HttpMethod_Delete) != methods.end()) - { - AddMethod(s, "DELETE"); - } - - return s; - } - - - - bool RestApi::Handle(HttpOutput& output, - RequestOrigin origin, - const char* remoteIp, - const char* username, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers, - const GetArguments& getArguments, - const void* bodyData, - size_t bodySize) - { - RestApiOutput wrappedOutput(output, method); - -#if ORTHANC_ENABLE_PUGIXML == 1 - { - // Look if the client wishes XML answers instead of JSON - // http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#z3 - Arguments::const_iterator it = headers.find("accept"); - if (it != headers.end()) - { - std::vector accepted; - Toolbox::TokenizeString(accepted, it->second, ';'); - for (size_t i = 0; i < accepted.size(); i++) - { - if (accepted[i] == MIME_XML) - { - wrappedOutput.SetConvertJsonToXml(true); - } - - if (accepted[i] == MIME_JSON) - { - wrappedOutput.SetConvertJsonToXml(false); - } - } - } - } -#endif - - Arguments compiled; - HttpToolbox::CompileGetArguments(compiled, getArguments); - - HttpHandlerVisitor visitor(*this, wrappedOutput, origin, remoteIp, username, - method, headers, compiled, bodyData, bodySize); - - if (root_.LookupResource(uri, visitor)) - { - wrappedOutput.Finalize(); - return true; - } - - std::set methods; - root_.GetAcceptedMethods(methods, uri); - - if (methods.empty()) - { - return false; // This URI is not served by this REST API - } - else - { - LOG(INFO) << "REST method " << EnumerationToString(method) - << " not allowed on: " << Toolbox::FlattenUri(uri); - - output.SendMethodNotAllowed(MethodsToString(methods)); - - return true; - } - } - - void RestApi::Register(const std::string& path, - RestApiGetCall::Handler handler) - { - root_.Register(path, handler); - } - - void RestApi::Register(const std::string& path, - RestApiPutCall::Handler handler) - { - root_.Register(path, handler); - } - - void RestApi::Register(const std::string& path, - RestApiPostCall::Handler handler) - { - root_.Register(path, handler); - } - - void RestApi::Register(const std::string& path, - RestApiDeleteCall::Handler handler) - { - root_.Register(path, handler); - } - - void RestApi::AutoListChildren(RestApiGetCall& call) - { - RestApi& context = call.GetContext(); - - Json::Value directory; - if (context.root_.GetDirectory(directory, call.GetFullUri())) - { - call.GetOutput().AnswerJson(directory); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApi.h --- a/Core/RestApi/RestApi.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "RestApiHierarchy.h" -#include "../Compatibility.h" - -#include - -namespace Orthanc -{ - class RestApi : public IHttpHandler - { - private: - RestApiHierarchy root_; - - public: - static void AutoListChildren(RestApiGetCall& call); - - virtual bool CreateChunkedRequestReader(std::unique_ptr& target, - RequestOrigin origin, - const char* remoteIp, - const char* username, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers) - { - return false; - } - - virtual bool Handle(HttpOutput& output, - RequestOrigin origin, - const char* remoteIp, - const char* username, - HttpMethod method, - const UriComponents& uri, - const Arguments& headers, - const GetArguments& getArguments, - const void* bodyData, - size_t bodySize); - - void Register(const std::string& path, - RestApiGetCall::Handler handler); - - void Register(const std::string& path, - RestApiPutCall::Handler handler); - - void Register(const std::string& path, - RestApiPostCall::Handler handler); - - void Register(const std::string& path, - RestApiDeleteCall::Handler handler); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiCall.cpp --- a/Core/RestApi/RestApiCall.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "RestApiCall.h" - -namespace Orthanc -{ - bool RestApiCall::ParseJsonRequestInternal(Json::Value& result, - const void* body, - size_t size) - { - result.clear(); - Json::Reader reader; - return reader.parse(reinterpret_cast(body), - reinterpret_cast(body) + size, result); - } - - - std::string RestApiCall::FlattenUri() const - { - std::string s = "/"; - - for (size_t i = 0; i < fullUri_.size(); i++) - { - s += fullUri_[i] + "/"; - } - - return s; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiCall.h --- a/Core/RestApi/RestApiCall.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../HttpServer/IHttpHandler.h" -#include "../HttpServer/HttpToolbox.h" -#include "RestApiPath.h" -#include "RestApiOutput.h" - -#include - -namespace Orthanc -{ - class RestApi; - - class RestApiCall : public boost::noncopyable - { - private: - RestApiOutput& output_; - RestApi& context_; - RequestOrigin origin_; - const char* remoteIp_; - const char* username_; - const IHttpHandler::Arguments& httpHeaders_; - const IHttpHandler::Arguments& uriComponents_; - const UriComponents& trailing_; - const UriComponents& fullUri_; - - protected: - static bool ParseJsonRequestInternal(Json::Value& result, - const void* body, - size_t size); - - public: - RestApiCall(RestApiOutput& output, - RestApi& context, - RequestOrigin origin, - const char* remoteIp, - const char* username, - const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::Arguments& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri) : - output_(output), - context_(context), - origin_(origin), - remoteIp_(remoteIp), - username_(username), - httpHeaders_(httpHeaders), - uriComponents_(uriComponents), - trailing_(trailing), - fullUri_(fullUri) - { - } - - RestApiOutput& GetOutput() - { - return output_; - } - - RestApi& GetContext() - { - return context_; - } - - const UriComponents& GetFullUri() const - { - return fullUri_; - } - - const UriComponents& GetTrailingUri() const - { - return trailing_; - } - - std::string GetUriComponent(const std::string& name, - const std::string& defaultValue) const - { - return HttpToolbox::GetArgument(uriComponents_, name, defaultValue); - } - - std::string GetHttpHeader(const std::string& name, - const std::string& defaultValue) const - { - return HttpToolbox::GetArgument(httpHeaders_, name, defaultValue); - } - - const IHttpHandler::Arguments& GetHttpHeaders() const - { - return httpHeaders_; - } - - void ParseCookies(IHttpHandler::Arguments& result) const - { - HttpToolbox::ParseCookies(result, httpHeaders_); - } - - std::string FlattenUri() const; - - RequestOrigin GetRequestOrigin() const - { - return origin_; - } - - const char* GetRemoteIp() const - { - return remoteIp_; - } - - const char* GetUsername() const - { - return username_; - } - - virtual bool ParseJsonRequest(Json::Value& result) const = 0; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiDeleteCall.h --- a/Core/RestApi/RestApiDeleteCall.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,65 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "RestApiCall.h" - -namespace Orthanc -{ - class RestApiDeleteCall : public RestApiCall - { - public: - typedef void (*Handler) (RestApiDeleteCall& call); - - RestApiDeleteCall(RestApiOutput& output, - RestApi& context, - RequestOrigin origin, - const char* remoteIp, - const char* username, - const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::Arguments& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri) : - RestApiCall(output, context, origin, remoteIp, username, - httpHeaders, uriComponents, trailing, fullUri) - { - } - - virtual bool ParseJsonRequest(Json::Value& result) const - { - result.clear(); - return true; - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiGetCall.cpp --- a/Core/RestApi/RestApiGetCall.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "RestApiGetCall.h" - -namespace Orthanc -{ - bool RestApiGetCall::ParseJsonRequest(Json::Value& result) const - { - result.clear(); - - for (IHttpHandler::Arguments::const_iterator - it = getArguments_.begin(); it != getArguments_.end(); ++it) - { - result[it->first] = it->second; - } - - return true; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiGetCall.h --- a/Core/RestApi/RestApiGetCall.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,77 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "RestApiCall.h" - -namespace Orthanc -{ - class RestApiGetCall : public RestApiCall - { - private: - const IHttpHandler::Arguments& getArguments_; - - public: - typedef void (*Handler) (RestApiGetCall& call); - - RestApiGetCall(RestApiOutput& output, - RestApi& context, - RequestOrigin origin, - const char* remoteIp, - const char* username, - const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::Arguments& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri, - const IHttpHandler::Arguments& getArguments) : - RestApiCall(output, context, origin, remoteIp, username, - httpHeaders, uriComponents, trailing, fullUri), - getArguments_(getArguments) - { - } - - std::string GetArgument(const std::string& name, - const std::string& defaultValue) const - { - return HttpToolbox::GetArgument(getArguments_, name, defaultValue); - } - - bool HasArgument(const std::string& name) const - { - return getArguments_.find(name) != getArguments_.end(); - } - - virtual bool ParseJsonRequest(Json::Value& result) const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiHierarchy.cpp --- a/Core/RestApi/RestApiHierarchy.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,476 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "RestApiHierarchy.h" - -#include "../OrthancException.h" - -#include -#include - -namespace Orthanc -{ - RestApiHierarchy::Resource::Resource() : - getHandler_(NULL), - postHandler_(NULL), - putHandler_(NULL), - deleteHandler_(NULL) - { - } - - - bool RestApiHierarchy::Resource::HasHandler(HttpMethod method) const - { - switch (method) - { - case HttpMethod_Get: - return getHandler_ != NULL; - - case HttpMethod_Post: - return postHandler_ != NULL; - - case HttpMethod_Put: - return putHandler_ != NULL; - - case HttpMethod_Delete: - return deleteHandler_ != NULL; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - bool RestApiHierarchy::Resource::IsEmpty() const - { - return (getHandler_ == NULL && - postHandler_ == NULL && - putHandler_ == NULL && - deleteHandler_ == NULL); - } - - - RestApiHierarchy& RestApiHierarchy::AddChild(Children& children, - const std::string& name) - { - Children::iterator it = children.find(name); - - if (it == children.end()) - { - // Create new child - RestApiHierarchy *child = new RestApiHierarchy; - children[name] = child; - return *child; - } - else - { - return *it->second; - } - } - - - - bool RestApiHierarchy::Resource::Handle(RestApiGetCall& call) const - { - if (getHandler_ != NULL) - { - getHandler_(call); - return true; - } - else - { - return false; - } - } - - - bool RestApiHierarchy::Resource::Handle(RestApiPutCall& call) const - { - if (putHandler_ != NULL) - { - putHandler_(call); - return true; - } - else - { - return false; - } - } - - - bool RestApiHierarchy::Resource::Handle(RestApiPostCall& call) const - { - if (postHandler_ != NULL) - { - postHandler_(call); - return true; - } - else - { - return false; - } - } - - - bool RestApiHierarchy::Resource::Handle(RestApiDeleteCall& call) const - { - if (deleteHandler_ != NULL) - { - deleteHandler_(call); - return true; - } - else - { - return false; - } - } - - - - void RestApiHierarchy::DeleteChildren(Children& children) - { - for (Children::iterator it = children.begin(); - it != children.end(); ++it) - { - delete it->second; - } - } - - - template - void RestApiHierarchy::RegisterInternal(const RestApiPath& path, - Handler handler, - size_t level) - { - if (path.GetLevelCount() == level) - { - if (path.IsUniversalTrailing()) - { - universalHandlers_.Register(handler); - } - else - { - handlers_.Register(handler); - } - } - else - { - RestApiHierarchy* child; - if (path.IsWildcardLevel(level)) - { - child = &AddChild(wildcardChildren_, path.GetWildcardName(level)); - } - else - { - child = &AddChild(children_, path.GetLevelName(level)); - } - - child->RegisterInternal(path, handler, level + 1); - } - } - - - bool RestApiHierarchy::LookupResource(IHttpHandler::Arguments& components, - const UriComponents& uri, - IVisitor& visitor, - size_t level) - { - if (uri.size() != 0 && - level > uri.size()) - { - return false; - } - - UriComponents trailing; - - // Look for an exact match on the resource of interest - if (uri.size() == 0 || - level == uri.size()) - { - if (!handlers_.IsEmpty() && - visitor.Visit(handlers_, uri, components, trailing)) - { - return true; - } - } - - - if (level < uri.size()) // A recursive call is possible - { - // Try and go down in the hierarchy, using an exact match for the child - Children::const_iterator child = children_.find(uri[level]); - if (child != children_.end()) - { - if (child->second->LookupResource(components, uri, visitor, level + 1)) - { - return true; - } - } - - // Try and go down in the hierarchy, using wildcard rules for children - for (child = wildcardChildren_.begin(); - child != wildcardChildren_.end(); ++child) - { - IHttpHandler::Arguments subComponents = components; - subComponents[child->first] = uri[level]; - - if (child->second->LookupResource(subComponents, uri, visitor, level + 1)) - { - return true; - } - } - } - - - // As a last resort, call the universal handlers, if any - if (!universalHandlers_.IsEmpty()) - { - trailing.resize(uri.size() - level); - size_t pos = 0; - for (size_t i = level; i < uri.size(); i++, pos++) - { - trailing[pos] = uri[i]; - } - - assert(pos == trailing.size()); - - if (visitor.Visit(universalHandlers_, uri, components, trailing)) - { - return true; - } - } - - return false; - } - - - bool RestApiHierarchy::CanGenerateDirectory() const - { - return (universalHandlers_.IsEmpty() && - wildcardChildren_.empty()); - } - - - bool RestApiHierarchy::GetDirectory(Json::Value& result, - const UriComponents& uri, - size_t level) - { - if (uri.size() == level) - { - if (CanGenerateDirectory()) - { - result = Json::arrayValue; - - for (Children::const_iterator it = children_.begin(); - it != children_.end(); ++it) - { - result.append(it->first); - } - - return true; - } - else - { - return false; - } - } - - Children::const_iterator child = children_.find(uri[level]); - if (child != children_.end()) - { - if (child->second->GetDirectory(result, uri, level + 1)) - { - return true; - } - } - - for (child = wildcardChildren_.begin(); - child != wildcardChildren_.end(); ++child) - { - if (child->second->GetDirectory(result, uri, level + 1)) - { - return true; - } - } - - return false; - } - - - RestApiHierarchy::~RestApiHierarchy() - { - DeleteChildren(children_); - DeleteChildren(wildcardChildren_); - } - - void RestApiHierarchy::Register(const std::string& uri, - RestApiGetCall::Handler handler) - { - RestApiPath path(uri); - RegisterInternal(path, handler, 0); - } - - void RestApiHierarchy::Register(const std::string& uri, - RestApiPutCall::Handler handler) - { - RestApiPath path(uri); - RegisterInternal(path, handler, 0); - } - - void RestApiHierarchy::Register(const std::string& uri, - RestApiPostCall::Handler handler) - { - RestApiPath path(uri); - RegisterInternal(path, handler, 0); - } - - void RestApiHierarchy::Register(const std::string& uri, - RestApiDeleteCall::Handler handler) - { - RestApiPath path(uri); - RegisterInternal(path, handler, 0); - } - - void RestApiHierarchy::CreateSiteMap(Json::Value& target) const - { - target = Json::objectValue; - - /*std::string s = " "; - if (handlers_.HasHandler(HttpMethod_Get)) - { - s += "GET "; - } - - if (handlers_.HasHandler(HttpMethod_Post)) - { - s += "POST "; - } - - if (handlers_.HasHandler(HttpMethod_Put)) - { - s += "PUT "; - } - - if (handlers_.HasHandler(HttpMethod_Delete)) - { - s += "DELETE "; - } - - target = s;*/ - - for (Children::const_iterator it = children_.begin(); - it != children_.end(); ++it) - { - it->second->CreateSiteMap(target[it->first]); - } - - for (Children::const_iterator it = wildcardChildren_.begin(); - it != wildcardChildren_.end(); ++it) - { - it->second->CreateSiteMap(target["<" + it->first + ">"]); - } - } - - - bool RestApiHierarchy::LookupResource(const UriComponents& uri, - IVisitor& visitor) - { - IHttpHandler::Arguments components; - return LookupResource(components, uri, visitor, 0); - } - - - - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - - class AcceptedMethodsVisitor : public RestApiHierarchy::IVisitor - { - private: - std::set& methods_; - - public: - AcceptedMethodsVisitor(std::set& methods) : methods_(methods) - { - } - - virtual bool Visit(const RestApiHierarchy::Resource& resource, - const UriComponents& uri, - const IHttpHandler::Arguments& components, - const UriComponents& trailing) - { - if (trailing.size() == 0) // Ignore universal handlers - { - if (resource.HasHandler(HttpMethod_Get)) - { - methods_.insert(HttpMethod_Get); - } - - if (resource.HasHandler(HttpMethod_Post)) - { - methods_.insert(HttpMethod_Post); - } - - if (resource.HasHandler(HttpMethod_Put)) - { - methods_.insert(HttpMethod_Put); - } - - if (resource.HasHandler(HttpMethod_Delete)) - { - methods_.insert(HttpMethod_Delete); - } - } - - return false; // Continue to check all the possible ways to access this URI - } - }; - } - - void RestApiHierarchy::GetAcceptedMethods(std::set& methods, - const UriComponents& uri) - { - IHttpHandler::Arguments components; - AcceptedMethodsVisitor visitor(methods); - if (LookupResource(components, uri, visitor, 0)) - { - Json::Value d; - if (GetDirectory(d, uri)) - { - methods.insert(HttpMethod_Get); - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiHierarchy.h --- a/Core/RestApi/RestApiHierarchy.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,165 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "RestApiGetCall.h" -#include "RestApiPostCall.h" -#include "RestApiPutCall.h" -#include "RestApiDeleteCall.h" - -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC RestApiHierarchy : public boost::noncopyable - { - public: - class ORTHANC_PUBLIC Resource : public boost::noncopyable - { - private: - RestApiGetCall::Handler getHandler_; - RestApiPostCall::Handler postHandler_; - RestApiPutCall::Handler putHandler_; - RestApiDeleteCall::Handler deleteHandler_; - - public: - Resource(); - - bool HasHandler(HttpMethod method) const; - - void Register(RestApiGetCall::Handler handler) - { - getHandler_ = handler; - } - - void Register(RestApiPutCall::Handler handler) - { - putHandler_ = handler; - } - - void Register(RestApiPostCall::Handler handler) - { - postHandler_ = handler; - } - - void Register(RestApiDeleteCall::Handler handler) - { - deleteHandler_ = handler; - } - - bool IsEmpty() const; - - bool Handle(RestApiGetCall& call) const; - - bool Handle(RestApiPutCall& call) const; - - bool Handle(RestApiPostCall& call) const; - - bool Handle(RestApiDeleteCall& call) const; - }; - - - class IVisitor : public boost::noncopyable - { - public: - virtual ~IVisitor() - { - } - - virtual bool Visit(const Resource& resource, - const UriComponents& uri, - const IHttpHandler::Arguments& components, - const UriComponents& trailing) = 0; - }; - - - private: - typedef std::map Children; - - Resource handlers_; - Children children_; - Children wildcardChildren_; - Resource universalHandlers_; - - static RestApiHierarchy& AddChild(Children& children, - const std::string& name); - - static void DeleteChildren(Children& children); - - template - void RegisterInternal(const RestApiPath& path, - Handler handler, - size_t level); - - bool CanGenerateDirectory() const; - - bool LookupResource(IHttpHandler::Arguments& components, - const UriComponents& uri, - IVisitor& visitor, - size_t level); - - bool GetDirectory(Json::Value& result, - const UriComponents& uri, - size_t level); - - public: - ~RestApiHierarchy(); - - void Register(const std::string& uri, - RestApiGetCall::Handler handler); - - void Register(const std::string& uri, - RestApiPutCall::Handler handler); - - void Register(const std::string& uri, - RestApiPostCall::Handler handler); - - void Register(const std::string& uri, - RestApiDeleteCall::Handler handler); - - void CreateSiteMap(Json::Value& target) const; - - bool GetDirectory(Json::Value& result, - const UriComponents& uri) - { - return GetDirectory(result, uri, 0); - } - - bool LookupResource(const UriComponents& uri, - IVisitor& visitor); - - void GetAcceptedMethods(std::set& methods, - const UriComponents& uri); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiOutput.cpp --- a/Core/RestApi/RestApiOutput.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,222 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "RestApiOutput.h" - -#include "../Logging.h" -#include "../OrthancException.h" -#include "../Toolbox.h" - -#include - - -namespace Orthanc -{ - RestApiOutput::RestApiOutput(HttpOutput& output, - HttpMethod method) : - output_(output), - method_(method), - convertJsonToXml_(false) - { - alreadySent_ = false; - } - - RestApiOutput::~RestApiOutput() - { - } - - void RestApiOutput::Finalize() - { - if (!alreadySent_) - { - if (method_ == HttpMethod_Post) - { - output_.SendStatus(HttpStatus_400_BadRequest); - } - else - { - output_.SendStatus(HttpStatus_404_NotFound); - } - } - } - - void RestApiOutput::CheckStatus() - { - if (alreadySent_) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls); - } - } - - - void RestApiOutput::AnswerStream(IHttpStreamAnswer& stream) - { - CheckStatus(); - output_.Answer(stream); - alreadySent_ = true; - } - - void RestApiOutput::AnswerJson(const Json::Value& value) - { - CheckStatus(); - - if (convertJsonToXml_) - { -#if ORTHANC_ENABLE_PUGIXML == 1 - std::string s; - Toolbox::JsonToXml(s, value); - - output_.SetContentType(MIME_XML_UTF8); - output_.Answer(s); -#else - throw OrthancException(ErrorCode_InternalError, - "Orthanc was compiled without XML support"); -#endif - } - else - { - Json::StyledWriter writer; - std::string s = writer.write(value); - - output_.SetContentType(MIME_JSON_UTF8); - output_.Answer(s); - } - - alreadySent_ = true; - } - - void RestApiOutput::AnswerBuffer(const std::string& buffer, - MimeType contentType) - { - AnswerBuffer(buffer.size() == 0 ? NULL : buffer.c_str(), - buffer.size(), contentType); - } - - void RestApiOutput::AnswerBuffer(const void* buffer, - size_t length, - MimeType contentType) - { - CheckStatus(); - - if (convertJsonToXml_ && - contentType == MimeType_Json) - { - Json::Value json; - Json::Reader reader; - if (reader.parse(reinterpret_cast(buffer), - reinterpret_cast(buffer) + length, json)) - { - AnswerJson(json); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat, - "The REST API tries and answers with an invalid JSON file"); - } - } - else - { - output_.SetContentType(contentType); - output_.Answer(buffer, length); - alreadySent_ = true; - } - } - - void RestApiOutput::Redirect(const std::string& path) - { - CheckStatus(); - output_.Redirect(path); - alreadySent_ = true; - } - - void RestApiOutput::SignalErrorInternal(HttpStatus status, - const char* message, - size_t messageSize) - { - if (status != HttpStatus_400_BadRequest && - status != HttpStatus_403_Forbidden && - status != HttpStatus_500_InternalServerError && - status != HttpStatus_415_UnsupportedMediaType) - { - throw OrthancException(ErrorCode_BadHttpStatusInRest); - } - - CheckStatus(); - output_.SendStatus(status, message, messageSize); - alreadySent_ = true; - } - - void RestApiOutput::SignalError(HttpStatus status) - { - SignalErrorInternal(status, NULL, 0); - } - - void RestApiOutput::SignalError(HttpStatus status, - const std::string& message) - { - SignalErrorInternal(status, message.c_str(), message.size()); - } - - void RestApiOutput::SetCookie(const std::string& name, - const std::string& value, - unsigned int maxAge) - { - if (name.find(";") != std::string::npos || - name.find(" ") != std::string::npos || - value.find(";") != std::string::npos || - value.find(" ") != std::string::npos) - { - throw OrthancException(ErrorCode_NotImplemented); - } - - CheckStatus(); - - std::string v = value + ";path=/"; - - if (maxAge != 0) - { - v += ";max-age=" + boost::lexical_cast(maxAge); - } - - output_.SetCookie(name, v); - } - - void RestApiOutput::ResetCookie(const std::string& name) - { - // This marks the cookie to be deleted by the browser in 1 second, - // and before it actually gets deleted, its value is set to the - // empty string - SetCookie(name, "", 1); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiOutput.h --- a/Core/RestApi/RestApiOutput.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,99 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../HttpServer/HttpOutput.h" -#include "../HttpServer/HttpFileSender.h" - -#include - -namespace Orthanc -{ - class RestApiOutput - { - private: - HttpOutput& output_; - HttpMethod method_; - bool alreadySent_; - bool convertJsonToXml_; - - void CheckStatus(); - - void SignalErrorInternal(HttpStatus status, - const char* message, - size_t messageSize); - - public: - RestApiOutput(HttpOutput& output, - HttpMethod method); - - ~RestApiOutput(); - - void SetConvertJsonToXml(bool convert) - { - convertJsonToXml_ = convert; - } - - bool IsConvertJsonToXml() const - { - return convertJsonToXml_; - } - - void AnswerStream(IHttpStreamAnswer& stream); - - void AnswerJson(const Json::Value& value); - - void AnswerBuffer(const std::string& buffer, - MimeType contentType); - - void AnswerBuffer(const void* buffer, - size_t length, - MimeType contentType); - - void SignalError(HttpStatus status); - - void SignalError(HttpStatus status, - const std::string& message); - - void Redirect(const std::string& path); - - void SetCookie(const std::string& name, - const std::string& value, - unsigned int maxAge = 0); - - void ResetCookie(const std::string& name); - - void Finalize(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiPath.cpp --- a/Core/RestApi/RestApiPath.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,181 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "../PrecompiledHeaders.h" -#include "RestApiPath.h" - -#include "../OrthancException.h" - -#include - -namespace Orthanc -{ - RestApiPath::RestApiPath(const std::string& uri) - { - Toolbox::SplitUriComponents(uri_, uri); - - if (uri_.size() == 0) - { - hasTrailing_ = false; - return; - } - - if (uri_.back() == "*") - { - hasTrailing_ = true; - uri_.pop_back(); - } - else - { - hasTrailing_ = false; - } - - components_.resize(uri_.size()); - for (size_t i = 0; i < uri_.size(); i++) - { - size_t s = uri_[i].size(); - assert(s > 0); - - if (uri_[i][0] == '{' && - uri_[i][s - 1] == '}') - { - components_[i] = uri_[i].substr(1, s - 2); - uri_[i] = ""; - } - else - { - components_[i] = ""; - } - } - } - - bool RestApiPath::Match(IHttpHandler::Arguments& components, - UriComponents& trailing, - const std::string& uriRaw) const - { - UriComponents uri; - Toolbox::SplitUriComponents(uri, uriRaw); - return Match(components, trailing, uri); - } - - bool RestApiPath::Match(IHttpHandler::Arguments& components, - UriComponents& trailing, - const UriComponents& uri) const - { - assert(uri_.size() == components_.size()); - - if (uri.size() < uri_.size()) - { - return false; - } - - if (!hasTrailing_ && uri.size() > uri_.size()) - { - return false; - } - - components.clear(); - trailing.clear(); - - assert(uri_.size() <= uri.size()); - for (size_t i = 0; i < uri_.size(); i++) - { - if (components_[i].size() == 0) - { - // This URI component is not a free parameter - if (uri_[i] != uri[i]) - { - return false; - } - } - else - { - // This URI component is a free parameter - components[components_[i]] = uri[i]; - } - } - - if (hasTrailing_) - { - trailing.assign(uri.begin() + uri_.size(), uri.end()); - } - - return true; - } - - - bool RestApiPath::Match(const UriComponents& uri) const - { - IHttpHandler::Arguments components; - UriComponents trailing; - return Match(components, trailing, uri); - } - - - bool RestApiPath::IsWildcardLevel(size_t level) const - { - assert(uri_.size() == components_.size()); - - if (level >= uri_.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - return uri_[level].length() == 0; - } - - const std::string& RestApiPath::GetWildcardName(size_t level) const - { - assert(uri_.size() == components_.size()); - - if (!IsWildcardLevel(level)) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - return components_[level]; - } - - const std::string& RestApiPath::GetLevelName(size_t level) const - { - assert(uri_.size() == components_.size()); - - if (IsWildcardLevel(level)) - { - throw OrthancException(ErrorCode_BadParameterType); - } - - return uri_[level]; - } -} - diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiPath.h --- a/Core/RestApi/RestApiPath.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "../Toolbox.h" -#include "../HttpServer/IHttpHandler.h" - -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC RestApiPath : public boost::noncopyable - { - private: - UriComponents uri_; - bool hasTrailing_; - std::vector components_; - - public: - RestApiPath(const std::string& uri); - - // This version is slower - bool Match(IHttpHandler::Arguments& components, - UriComponents& trailing, - const std::string& uriRaw) const; - - bool Match(IHttpHandler::Arguments& components, - UriComponents& trailing, - const UriComponents& uri) const; - - bool Match(const UriComponents& uri) const; - - size_t GetLevelCount() const - { - return uri_.size(); - } - - bool IsWildcardLevel(size_t level) const; - - bool IsUniversalTrailing() const - { - return hasTrailing_; - } - - const std::string& GetWildcardName(size_t level) const; - - const std::string& GetLevelName(size_t level) const; - - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiPostCall.h --- a/Core/RestApi/RestApiPostCall.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "RestApiCall.h" - -namespace Orthanc -{ - class RestApiPostCall : public RestApiCall - { - private: - const void* bodyData_; - size_t bodySize_; - - public: - typedef void (*Handler) (RestApiPostCall& call); - - RestApiPostCall(RestApiOutput& output, - RestApi& context, - RequestOrigin origin, - const char* remoteIp, - const char* username, - const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::Arguments& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri, - const void* bodyData, - size_t bodySize) : - RestApiCall(output, context, origin, remoteIp, username, - httpHeaders, uriComponents, trailing, fullUri), - bodyData_(bodyData), - bodySize_(bodySize) - { - } - - const void* GetBodyData() const - { - return bodyData_; - } - - size_t GetBodySize() const - { - return bodySize_; - } - - void BodyToString(std::string& result) const - { - result.assign(reinterpret_cast(bodyData_), bodySize_); - } - - virtual bool ParseJsonRequest(Json::Value& result) const - { - return ParseJsonRequestInternal(result, reinterpret_cast(bodyData_), bodySize_); - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/RestApi/RestApiPutCall.h --- a/Core/RestApi/RestApiPutCall.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "RestApiCall.h" - -namespace Orthanc -{ - class RestApiPutCall : public RestApiCall - { - private: - const void* bodyData_; - size_t bodySize_; - - public: - typedef void (*Handler) (RestApiPutCall& call); - - RestApiPutCall(RestApiOutput& output, - RestApi& context, - RequestOrigin origin, - const char* remoteIp, - const char* username, - const IHttpHandler::Arguments& httpHeaders, - const IHttpHandler::Arguments& uriComponents, - const UriComponents& trailing, - const UriComponents& fullUri, - const void* bodyData, - size_t bodySize) : - RestApiCall(output, context, origin, remoteIp, username, - httpHeaders, uriComponents, trailing, fullUri), - bodyData_(bodyData), - bodySize_(bodySize) - { - } - - const void* GetBodyData() const - { - return bodyData_; - } - - size_t GetBodySize() const - { - return bodySize_; - } - - void BodyToString(std::string& result) const - { - result.assign(reinterpret_cast(bodyData_), bodySize_); - } - - virtual bool ParseJsonRequest(Json::Value& result) const - { - return ParseJsonRequestInternal(result, reinterpret_cast(bodyData_), bodySize_); - } - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/Connection.cpp --- a/Core/SQLite/Connection.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,399 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#if ORTHANC_SQLITE_STANDALONE != 1 -#include "../PrecompiledHeaders.h" -#endif - -#include "Connection.h" -#include "OrthancSQLiteException.h" - -#include -#include -#include - -#if ORTHANC_SQLITE_STANDALONE != 1 -#include "../Logging.h" -#endif - -#include "sqlite3.h" - - -namespace Orthanc -{ - namespace SQLite - { - Connection::Connection() : - db_(NULL), - transactionNesting_(0), - needsRollback_(false) - { - } - - - Connection::~Connection() - { - Close(); - } - - - void Connection::CheckIsOpen() const - { - if (!db_) - { - throw OrthancSQLiteException(ErrorCode_SQLiteNotOpened); - } - } - - void Connection::Open(const std::string& path) - { - if (db_) - { - throw OrthancSQLiteException(ErrorCode_SQLiteAlreadyOpened); - } - - int err = sqlite3_open(path.c_str(), &db_); - if (err != SQLITE_OK) - { - Close(); - db_ = NULL; - throw OrthancSQLiteException(ErrorCode_SQLiteCannotOpen); - } - - // Execute PRAGMAs at this point - // http://www.sqlite.org/pragma.html - Execute("PRAGMA FOREIGN_KEYS=ON;"); - Execute("PRAGMA RECURSIVE_TRIGGERS=ON;"); - } - - void Connection::OpenInMemory() - { - Open(":memory:"); - } - - void Connection::Close() - { - ClearCache(); - - if (db_) - { - sqlite3_close(db_); - db_ = NULL; - } - } - - void Connection::ClearCache() - { - for (CachedStatements::iterator - it = cachedStatements_.begin(); - it != cachedStatements_.end(); ++it) - { - delete it->second; - } - - cachedStatements_.clear(); - } - - - StatementReference& Connection::GetCachedStatement(const StatementId& id, - const char* sql) - { - CachedStatements::iterator i = cachedStatements_.find(id); - if (i != cachedStatements_.end()) - { - if (i->second->GetReferenceCount() >= 1) - { - throw OrthancSQLiteException(ErrorCode_SQLiteStatementAlreadyUsed); - } - - return *i->second; - } - else - { - StatementReference* statement = new StatementReference(db_, sql); - cachedStatements_[id] = statement; - return *statement; - } - } - - - bool Connection::Execute(const char* sql) - { -#if ORTHANC_SQLITE_STANDALONE != 1 - VLOG(1) << "SQLite::Connection::Execute " << sql; -#endif - - CheckIsOpen(); - - int error = sqlite3_exec(db_, sql, NULL, NULL, NULL); - if (error == SQLITE_ERROR) - { -#if ORTHANC_SQLITE_STANDALONE != 1 - LOG(ERROR) << "SQLite execute error: " << sqlite3_errmsg(db_) - << " (" << sqlite3_extended_errcode(db_) << ")"; -#endif - - throw OrthancSQLiteException(ErrorCode_SQLiteExecute); - } - else - { - return error == SQLITE_OK; - } - } - - int Connection::ExecuteAndReturnErrorCode(const char* sql) - { - CheckIsOpen(); - return sqlite3_exec(db_, sql, NULL, NULL, NULL); - } - - // Info querying ------------------------------------------------------------- - - bool Connection::IsSQLValid(const char* sql) - { - sqlite3_stmt* stmt = NULL; - if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK) - return false; - - sqlite3_finalize(stmt); - return true; - } - - bool Connection::DoesTableOrIndexExist(const char* name, - const char* type) const - { - // Our SQL is non-mutating, so this cast is OK. - Statement statement(const_cast(*this), - "SELECT name FROM sqlite_master WHERE type=? AND name=?"); - statement.BindString(0, type); - statement.BindString(1, name); - return statement.Step(); // Table exists if any row was returned. - } - - bool Connection::DoesTableExist(const char* table_name) const - { - return DoesTableOrIndexExist(table_name, "table"); - } - - bool Connection::DoesIndexExist(const char* index_name) const - { - return DoesTableOrIndexExist(index_name, "index"); - } - - bool Connection::DoesColumnExist(const char* table_name, const char* column_name) const - { - std::string sql("PRAGMA TABLE_INFO("); - sql.append(table_name); - sql.append(")"); - - // Our SQL is non-mutating, so this cast is OK. - Statement statement(const_cast(*this), sql.c_str()); - - while (statement.Step()) { - if (!statement.ColumnString(1).compare(column_name)) - return true; - } - return false; - } - - int64_t Connection::GetLastInsertRowId() const - { - return sqlite3_last_insert_rowid(db_); - } - - int Connection::GetLastChangeCount() const - { - return sqlite3_changes(db_); - } - - int Connection::GetErrorCode() const - { - return sqlite3_errcode(db_); - } - - int Connection::GetLastErrno() const - { - int err = 0; - if (SQLITE_OK != sqlite3_file_control(db_, NULL, SQLITE_LAST_ERRNO, &err)) - return -2; - - return err; - } - - const char* Connection::GetErrorMessage() const - { - return sqlite3_errmsg(db_); - } - - - bool Connection::BeginTransaction() - { - if (needsRollback_) - { - assert(transactionNesting_ > 0); - - // When we're going to rollback, fail on this begin and don't actually - // mark us as entering the nested transaction. - return false; - } - - bool success = true; - if (!transactionNesting_) - { - needsRollback_ = false; - - Statement begin(*this, SQLITE_FROM_HERE, "BEGIN TRANSACTION"); - if (!begin.Run()) - return false; - } - transactionNesting_++; - return success; - } - - void Connection::RollbackTransaction() - { - if (!transactionNesting_) - { - throw OrthancSQLiteException(ErrorCode_SQLiteRollbackWithoutTransaction); - } - - transactionNesting_--; - - if (transactionNesting_ > 0) - { - // Mark the outermost transaction as needing rollback. - needsRollback_ = true; - return; - } - - DoRollback(); - } - - bool Connection::CommitTransaction() - { - if (!transactionNesting_) - { - throw OrthancSQLiteException(ErrorCode_SQLiteCommitWithoutTransaction); - } - transactionNesting_--; - - if (transactionNesting_ > 0) - { - // Mark any nested transactions as failing after we've already got one. - return !needsRollback_; - } - - if (needsRollback_) - { - DoRollback(); - return false; - } - - Statement commit(*this, SQLITE_FROM_HERE, "COMMIT"); - return commit.Run(); - } - - void Connection::DoRollback() - { - Statement rollback(*this, SQLITE_FROM_HERE, "ROLLBACK"); - rollback.Run(); - needsRollback_ = false; - } - - - - - - - static void ScalarFunctionCaller(sqlite3_context* rawContext, - int argc, - sqlite3_value** argv) - { - FunctionContext context(rawContext, argc, argv); - - void* payload = sqlite3_user_data(rawContext); - assert(payload != NULL); - - IScalarFunction& func = *reinterpret_cast(payload); - func.Compute(context); - } - - - static void ScalarFunctionDestroyer(void* payload) - { - assert(payload != NULL); - delete reinterpret_cast(payload); - } - - - IScalarFunction* Connection::Register(IScalarFunction* func) - { - int err = sqlite3_create_function_v2(db_, - func->GetName(), - func->GetCardinality(), - SQLITE_UTF8, - func, - ScalarFunctionCaller, - NULL, - NULL, - ScalarFunctionDestroyer); - - if (err != SQLITE_OK) - { - delete func; - throw OrthancSQLiteException(ErrorCode_SQLiteRegisterFunction); - } - - return func; - } - - - void Connection::FlushToDisk() - { -#if ORTHANC_SQLITE_STANDALONE != 1 - VLOG(1) << "SQLite::Connection::FlushToDisk"; -#endif - - int err = sqlite3_wal_checkpoint(db_, NULL); - - if (err != SQLITE_OK) - { - throw OrthancSQLiteException(ErrorCode_SQLiteFlush); - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/Connection.h --- a/Core/SQLite/Connection.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,175 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -#include "Statement.h" -#include "IScalarFunction.h" -#include "SQLiteTypes.h" - -#include -#include - -#define SQLITE_FROM_HERE ::Orthanc::SQLite::StatementId(__FILE__, __LINE__) - -namespace Orthanc -{ - namespace SQLite - { - class ORTHANC_PUBLIC Connection : NonCopyable - { - friend class Statement; - friend class Transaction; - - private: - // All cached statements. Keeping a reference to these statements means that - // they'll remain active. - typedef std::map CachedStatements; - CachedStatements cachedStatements_; - - // The actual sqlite database. Will be NULL before Init has been called or if - // Init resulted in an error. - sqlite3* db_; - - // Number of currently-nested transactions. - int transactionNesting_; - - // True if any of the currently nested transactions have been rolled back. - // When we get to the outermost transaction, this will determine if we do - // a rollback instead of a commit. - bool needsRollback_; - - void ClearCache(); - - void CheckIsOpen() const; - - sqlite3* GetWrappedObject() - { - return db_; - } - - StatementReference& GetCachedStatement(const StatementId& id, - const char* sql); - - bool DoesTableOrIndexExist(const char* name, - const char* type) const; - - void DoRollback(); - - public: - // The database is opened by calling Open[InMemory](). Any uncommitted - // transactions will be rolled back when this object is deleted. - Connection(); - ~Connection(); - - void Open(const std::string& path); - - void OpenInMemory(); - - void Close(); - - bool Execute(const char* sql); - - bool Execute(const std::string& sql) - { - return Execute(sql.c_str()); - } - - void FlushToDisk(); - - IScalarFunction* Register(IScalarFunction* func); // Takes the ownership of the function - - // Info querying ------------------------------------------------------------- - - // Used to check a |sql| statement for syntactic validity. If the - // statement is valid SQL, returns true. - bool IsSQLValid(const char* sql); - - // Returns true if the given table exists. - bool DoesTableExist(const char* table_name) const; - - // Returns true if the given index exists. - bool DoesIndexExist(const char* index_name) const; - - // Returns true if a column with the given name exists in the given table. - bool DoesColumnExist(const char* table_name, const char* column_name) const; - - // Returns sqlite's internal ID for the last inserted row. Valid only - // immediately after an insert. - int64_t GetLastInsertRowId() const; - - // Returns sqlite's count of the number of rows modified by the last - // statement executed. Will be 0 if no statement has executed or the database - // is closed. - int GetLastChangeCount() const; - - // Errors -------------------------------------------------------------------- - - // Returns the error code associated with the last sqlite operation. - int GetErrorCode() const; - - // Returns the errno associated with GetErrorCode(). See - // SQLITE_LAST_ERRNO in SQLite documentation. - int GetLastErrno() const; - - // Returns a pointer to a statically allocated string associated with the - // last sqlite operation. - const char* GetErrorMessage() const; - - - // Diagnostics (for unit tests) ---------------------------------------------- - - int ExecuteAndReturnErrorCode(const char* sql); - - bool HasCachedStatement(const StatementId& id) const - { - return cachedStatements_.find(id) != cachedStatements_.end(); - } - - int GetTransactionNesting() const - { - return transactionNesting_; - } - - // Transactions -------------------------------------------------------------- - - bool BeginTransaction(); - void RollbackTransaction(); - bool CommitTransaction(); - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/FunctionContext.cpp --- a/Core/SQLite/FunctionContext.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of the CHU of Liege, nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#if ORTHANC_SQLITE_STANDALONE != 1 -#include "../PrecompiledHeaders.h" -#endif - -#include "FunctionContext.h" -#include "OrthancSQLiteException.h" - -#include - -#include "sqlite3.h" - -namespace Orthanc -{ - namespace SQLite - { - FunctionContext::FunctionContext(struct sqlite3_context* context, - int argc, - Internals::SQLiteValue** argv) - { - assert(context != NULL); - assert(argc >= 0); - assert(argv != NULL); - - context_ = context; - argc_ = static_cast(argc); - argv_ = argv; - } - - void FunctionContext::CheckIndex(unsigned int index) const - { - if (index >= argc_) - { - throw OrthancSQLiteException(ErrorCode_ParameterOutOfRange); - } - } - - ColumnType FunctionContext::GetColumnType(unsigned int index) const - { - CheckIndex(index); - return static_cast(sqlite3_value_type(argv_[index])); - } - - int FunctionContext::GetIntValue(unsigned int index) const - { - CheckIndex(index); - return sqlite3_value_int(argv_[index]); - } - - int64_t FunctionContext::GetInt64Value(unsigned int index) const - { - CheckIndex(index); - return sqlite3_value_int64(argv_[index]); - } - - double FunctionContext::GetDoubleValue(unsigned int index) const - { - CheckIndex(index); - return sqlite3_value_double(argv_[index]); - } - - std::string FunctionContext::GetStringValue(unsigned int index) const - { - CheckIndex(index); - return std::string(reinterpret_cast(sqlite3_value_text(argv_[index]))); - } - - bool FunctionContext::IsNullValue(unsigned int index) const - { - CheckIndex(index); - return sqlite3_value_type(argv_[index]) == SQLITE_NULL; - } - - void FunctionContext::SetNullResult() - { - sqlite3_result_null(context_); - } - - void FunctionContext::SetIntResult(int value) - { - sqlite3_result_int(context_, value); - } - - void FunctionContext::SetDoubleResult(double value) - { - sqlite3_result_double(context_, value); - } - - void FunctionContext::SetStringResult(const std::string& str) - { - sqlite3_result_text(context_, str.data(), static_cast(str.size()), SQLITE_TRANSIENT); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/FunctionContext.h --- a/Core/SQLite/FunctionContext.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of the CHU of Liege, nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -#include "Statement.h" - -namespace Orthanc -{ - namespace SQLite - { - class ORTHANC_PUBLIC FunctionContext : public NonCopyable - { - friend class Connection; - - private: - struct sqlite3_context* context_; - unsigned int argc_; - Internals::SQLiteValue** argv_; - - void CheckIndex(unsigned int index) const; - - public: - FunctionContext(struct sqlite3_context* context, - int argc, - Internals::SQLiteValue** argv); - - ColumnType GetColumnType(unsigned int index) const; - - unsigned int GetParameterCount() const - { - return argc_; - } - - int GetIntValue(unsigned int index) const; - - int64_t GetInt64Value(unsigned int index) const; - - double GetDoubleValue(unsigned int index) const; - - std::string GetStringValue(unsigned int index) const; - - bool IsNullValue(unsigned int index) const; - - void SetNullResult(); - - void SetIntResult(int value); - - void SetDoubleResult(double value); - - void SetStringResult(const std::string& str); - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/IScalarFunction.h --- a/Core/SQLite/IScalarFunction.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of the CHU of Liege, nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -#include "NonCopyable.h" -#include "FunctionContext.h" - -namespace Orthanc -{ - namespace SQLite - { - class IScalarFunction : public NonCopyable - { - public: - virtual ~IScalarFunction() - { - } - - virtual const char* GetName() const = 0; - - virtual unsigned int GetCardinality() const = 0; - - virtual void Compute(FunctionContext& context) = 0; - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/ITransaction.h --- a/Core/SQLite/ITransaction.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -#include "NonCopyable.h" - -namespace Orthanc -{ - namespace SQLite - { - class ITransaction : public NonCopyable - { - public: - virtual ~ITransaction() - { - } - - // Begins the transaction. This uses the default sqlite "deferred" transaction - // type, which means that the DB lock is lazily acquired the next time the - // database is accessed, not in the begin transaction command. - virtual void Begin() = 0; - - // Rolls back the transaction. This will happen automatically if you do - // nothing when the transaction goes out of scope. - virtual void Rollback() = 0; - - // Commits the transaction, returning true on success. - virtual void Commit() = 0; - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/NonCopyable.h --- a/Core/SQLite/NonCopyable.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -namespace Orthanc -{ - namespace SQLite - { - // This class mimics "boost::noncopyable" - class NonCopyable - { - private: - NonCopyable(const NonCopyable&); - - NonCopyable& operator= (const NonCopyable&); - - protected: - NonCopyable() - { - } - - ~NonCopyable() - { - } - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/OrthancSQLiteException.h --- a/Core/SQLite/OrthancSQLiteException.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - - -#if ORTHANC_ENABLE_SQLITE != 1 -# error Macro ORTHANC_ENABLE_SQLITE must be set to 1 to use SQLite -#endif - - -#if ORTHANC_SQLITE_STANDALONE == 1 -#include - -namespace Orthanc -{ - namespace SQLite - { - // Auto-generated by "Resources/GenerateErrorCodes.py" - enum ErrorCode - { - ErrorCode_ParameterOutOfRange, - ErrorCode_BadParameterType, - ErrorCode_SQLiteNotOpened, - ErrorCode_SQLiteAlreadyOpened, - ErrorCode_SQLiteCannotOpen, - ErrorCode_SQLiteStatementAlreadyUsed, - ErrorCode_SQLiteExecute, - ErrorCode_SQLiteRollbackWithoutTransaction, - ErrorCode_SQLiteCommitWithoutTransaction, - ErrorCode_SQLiteRegisterFunction, - ErrorCode_SQLiteFlush, - ErrorCode_SQLiteCannotRun, - ErrorCode_SQLiteCannotStep, - ErrorCode_SQLiteBindOutOfRange, - ErrorCode_SQLitePrepareStatement, - ErrorCode_SQLiteTransactionAlreadyStarted, - ErrorCode_SQLiteTransactionCommit, - ErrorCode_SQLiteTransactionBegin - }; - - class OrthancSQLiteException : public ::std::runtime_error - { - public: - OrthancSQLiteException(ErrorCode error) : - ::std::runtime_error(EnumerationToString(error)) - { - } - - // Auto-generated by "Resources/GenerateErrorCodes.py" - static const char* EnumerationToString(ErrorCode code) - { - switch (code) - { - case ErrorCode_ParameterOutOfRange: - return "Parameter out of range"; - - case ErrorCode_BadParameterType: - return "Bad type for a parameter"; - - case ErrorCode_SQLiteNotOpened: - return "SQLite: The database is not opened"; - - case ErrorCode_SQLiteAlreadyOpened: - return "SQLite: Connection is already open"; - - case ErrorCode_SQLiteCannotOpen: - return "SQLite: Unable to open the database"; - - case ErrorCode_SQLiteStatementAlreadyUsed: - return "SQLite: This cached statement is already being referred to"; - - case ErrorCode_SQLiteExecute: - return "SQLite: Cannot execute a command"; - - case ErrorCode_SQLiteRollbackWithoutTransaction: - return "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)"; - - case ErrorCode_SQLiteCommitWithoutTransaction: - return "SQLite: Committing a nonexistent transaction"; - - case ErrorCode_SQLiteRegisterFunction: - return "SQLite: Unable to register a function"; - - case ErrorCode_SQLiteFlush: - return "SQLite: Unable to flush the database"; - - case ErrorCode_SQLiteCannotRun: - return "SQLite: Cannot run a cached statement"; - - case ErrorCode_SQLiteCannotStep: - return "SQLite: Cannot step over a cached statement"; - - case ErrorCode_SQLiteBindOutOfRange: - return "SQLite: Bing a value while out of range (serious error)"; - - case ErrorCode_SQLitePrepareStatement: - return "SQLite: Cannot prepare a cached statement"; - - case ErrorCode_SQLiteTransactionAlreadyStarted: - return "SQLite: Beginning the same transaction twice"; - - case ErrorCode_SQLiteTransactionCommit: - return "SQLite: Failure when committing the transaction"; - - case ErrorCode_SQLiteTransactionBegin: - return "SQLite: Cannot start a transaction"; - - default: - return "Unknown error code"; - } - } - }; - } -} - -#else -# include "../OrthancException.h" -# define OrthancSQLiteException ::Orthanc::OrthancException -#endif diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/README.txt --- a/Core/SQLite/README.txt Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -Introduction -============ - -The code in this folder is a standalone object-oriented wrapper around -SQLite3. It is derived from the code of Chromium: - -http://src.chromium.org/viewvc/chrome/trunk/src/sql/ -http://maxradi.us/documents/sqlite/ - - -Main differences with Chromium -============================== - -* The reference counting mechanism has been reimplemented to make it - simpler. -* The OrthancException class is used for the exception mechanisms. -* A statement is always valid (is_valid() always return true). -* The classes and the methods have been renamed to meet Orthanc's - coding conventions. - - -Reuse in another software -========================= - -To use the Orthanc SQLite wrapper in another project than Orthanc, you -just have to define the "ORTHANC_SQLITE_STANDALONE" macro. - -All the C++ exceptions generated by the wrapper will be objects of the -class "::Orthanc::SQLite::OrthancSQLiteException", that derives from -the standard exception class "::std::runtime_error". - - -Licensing -========= - -The code in this folder is licensed under the 3-clause BSD license, in -order to respect the original license of the code. - -It is pretty straightforward to extract the code from this folder and -to include it in another project. diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/SQLiteTypes.h --- a/Core/SQLite/SQLiteTypes.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of the CHU of Liege, nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -struct sqlite3; -struct sqlite3_context; -struct sqlite3_stmt; - -#if !defined(ORTHANC_SQLITE_VERSION) -#error Please define macro ORTHANC_SQLITE_VERSION -#endif - - -/** - * "sqlite3_value" is defined as: - * - "typedef struct Mem sqlite3_value;" up to SQLite <= 3.18.2 - * - "typedef struct sqlite3_value sqlite3_value;" since SQLite >= 3.19.0. - * We create our own copy of this typedef to get around this API incompatibility. - * https://github.com/mackyle/sqlite/commit/db1d90df06a78264775a14d22c3361eb5b42be17 - **/ - -#if ORTHANC_SQLITE_VERSION < 3019000 -struct Mem; -#else -struct sqlite3_value; -#endif - -namespace Orthanc -{ - namespace SQLite - { - namespace Internals - { -#if ORTHANC_SQLITE_VERSION < 3019000 - typedef struct ::Mem SQLiteValue; -#else - typedef struct ::sqlite3_value SQLiteValue; -#endif - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/Statement.cpp --- a/Core/SQLite/Statement.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,370 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#if ORTHANC_SQLITE_STANDALONE != 1 -#include "../PrecompiledHeaders.h" -#endif - -#include "Statement.h" -#include "Connection.h" - -#include -#include -#include - -#if (ORTHANC_SQLITE_STANDALONE == 1) -// Trace logging is disabled if this SQLite wrapper is used -// independently of Orthanc -# define LOG_CREATE(message); -# define LOG_APPLY(message); -#elif defined(NDEBUG) -// Trace logging is disabled in release builds -# include "../Logging.h" -# define LOG_CREATE(message); -# define LOG_APPLY(message); -#else -// Trace logging is enabled in debug builds -# include "../Logging.h" -# define LOG_CREATE(message) VLOG(1) << "SQLite::Statement create: " << message; -# define LOG_APPLY(message); // VLOG(1) << "SQLite::Statement apply: " << message; -#endif - -#include "sqlite3.h" - -#if defined(_MSC_VER) -#define snprintf _snprintf -#endif - - -namespace Orthanc -{ - namespace SQLite - { - int Statement::CheckError(int err, ErrorCode code) const - { - bool succeeded = (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE); - if (!succeeded) - { -#if ORTHANC_SQLITE_STANDALONE != 1 - char buffer[128]; - snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err); - LOG(ERROR) << buffer; -#endif - - throw OrthancSQLiteException(code); - } - - return err; - } - - void Statement::CheckOk(int err, ErrorCode code) const - { - if (err == SQLITE_RANGE) - { - // Binding to a non-existent variable is evidence of a serious error. - throw OrthancSQLiteException(ErrorCode_SQLiteBindOutOfRange); - } - else if (err != SQLITE_OK) - { -#if ORTHANC_SQLITE_STANDALONE != 1 - char buffer[128]; - snprintf(buffer, sizeof(buffer) - 1, "SQLite error code %d", err); - LOG(ERROR) << buffer; -#endif - - throw OrthancSQLiteException(code); - } - } - - - Statement::Statement(Connection& database, - const StatementId& id, - const std::string& sql) : - reference_(database.GetCachedStatement(id, sql.c_str())) - { - Reset(true); - LOG_CREATE(sql); - } - - - Statement::Statement(Connection& database, - const StatementId& id, - const char* sql) : - reference_(database.GetCachedStatement(id, sql)) - { - Reset(true); - LOG_CREATE(sql); - } - - - Statement::Statement(Connection& database, - const std::string& sql) : - reference_(database.GetWrappedObject(), sql.c_str()) - { - LOG_CREATE(sql); - } - - - Statement::Statement(Connection& database, - const char* sql) : - reference_(database.GetWrappedObject(), sql) - { - LOG_CREATE(sql); - } - - - bool Statement::Run() - { - LOG_APPLY(sqlite3_sql(GetStatement())); - - return CheckError(sqlite3_step(GetStatement()), ErrorCode_SQLiteCannotRun) == SQLITE_DONE; - } - - bool Statement::Step() - { - LOG_APPLY(sqlite3_sql(GetStatement())); - - return CheckError(sqlite3_step(GetStatement()), ErrorCode_SQLiteCannotStep) == SQLITE_ROW; - } - - void Statement::Reset(bool clear_bound_vars) - { - // We don't call CheckError() here because sqlite3_reset() returns - // the last error that Step() caused thereby generating a second - // spurious error callback. - if (clear_bound_vars) - sqlite3_clear_bindings(GetStatement()); - //VLOG(1) << "SQLite::Statement::Reset"; - sqlite3_reset(GetStatement()); - } - - std::string Statement::GetOriginalSQLStatement() - { - return std::string(sqlite3_sql(GetStatement())); - } - - - void Statement::BindNull(int col) - { - CheckOk(sqlite3_bind_null(GetStatement(), col + 1), - ErrorCode_BadParameterType); - } - - void Statement::BindBool(int col, bool val) - { - BindInt(col, val ? 1 : 0); - } - - void Statement::BindInt(int col, int val) - { - CheckOk(sqlite3_bind_int(GetStatement(), col + 1, val), - ErrorCode_BadParameterType); - } - - void Statement::BindInt64(int col, int64_t val) - { - CheckOk(sqlite3_bind_int64(GetStatement(), col + 1, val), - ErrorCode_BadParameterType); - } - - void Statement::BindDouble(int col, double val) - { - CheckOk(sqlite3_bind_double(GetStatement(), col + 1, val), - ErrorCode_BadParameterType); - } - - void Statement::BindCString(int col, const char* val) - { - CheckOk(sqlite3_bind_text(GetStatement(), col + 1, val, -1, SQLITE_TRANSIENT), - ErrorCode_BadParameterType); - } - - void Statement::BindString(int col, const std::string& val) - { - CheckOk(sqlite3_bind_text(GetStatement(), - col + 1, - val.data(), - static_cast(val.size()), - SQLITE_TRANSIENT), - ErrorCode_BadParameterType); - } - - /*void Statement::BindString16(int col, const string16& value) - { - BindString(col, UTF16ToUTF8(value)); - }*/ - - void Statement::BindBlob(int col, const void* val, int val_len) - { - CheckOk(sqlite3_bind_blob(GetStatement(), col + 1, val, val_len, SQLITE_TRANSIENT), - ErrorCode_BadParameterType); - } - - - int Statement::ColumnCount() const - { - return sqlite3_column_count(GetStatement()); - } - - - ColumnType Statement::GetColumnType(int col) const - { - // Verify that our enum matches sqlite's values. - assert(COLUMN_TYPE_INTEGER == SQLITE_INTEGER); - assert(COLUMN_TYPE_FLOAT == SQLITE_FLOAT); - assert(COLUMN_TYPE_TEXT == SQLITE_TEXT); - assert(COLUMN_TYPE_BLOB == SQLITE_BLOB); - assert(COLUMN_TYPE_NULL == SQLITE_NULL); - - return static_cast(sqlite3_column_type(GetStatement(), col)); - } - - ColumnType Statement::GetDeclaredColumnType(int col) const - { - std::string column_type(sqlite3_column_decltype(GetStatement(), col)); - std::transform(column_type.begin(), column_type.end(), column_type.begin(), tolower); - - if (column_type == "integer") - return COLUMN_TYPE_INTEGER; - else if (column_type == "float") - return COLUMN_TYPE_FLOAT; - else if (column_type == "text") - return COLUMN_TYPE_TEXT; - else if (column_type == "blob") - return COLUMN_TYPE_BLOB; - - return COLUMN_TYPE_NULL; - } - - bool Statement::ColumnIsNull(int col) const - { - return sqlite3_column_type(GetStatement(), col) == SQLITE_NULL; - } - - bool Statement::ColumnBool(int col) const - { - return !!ColumnInt(col); - } - - int Statement::ColumnInt(int col) const - { - return sqlite3_column_int(GetStatement(), col); - } - - int64_t Statement::ColumnInt64(int col) const - { - return sqlite3_column_int64(GetStatement(), col); - } - - double Statement::ColumnDouble(int col) const - { - return sqlite3_column_double(GetStatement(), col); - } - - std::string Statement::ColumnString(int col) const - { - const char* str = reinterpret_cast( - sqlite3_column_text(GetStatement(), col)); - int len = sqlite3_column_bytes(GetStatement(), col); - - std::string result; - if (str && len > 0) - result.assign(str, len); - return result; - } - - /*string16 Statement::ColumnString16(int col) const - { - std::string s = ColumnString(col); - return !s.empty() ? UTF8ToUTF16(s) : string16(); - }*/ - - int Statement::ColumnByteLength(int col) const - { - return sqlite3_column_bytes(GetStatement(), col); - } - - const void* Statement::ColumnBlob(int col) const - { - return sqlite3_column_blob(GetStatement(), col); - } - - bool Statement::ColumnBlobAsString(int col, std::string* blob) - { - const void* p = ColumnBlob(col); - size_t len = ColumnByteLength(col); - blob->resize(len); - if (blob->size() != len) { - return false; - } - blob->assign(reinterpret_cast(p), len); - return true; - } - - /*bool Statement::ColumnBlobAsString16(int col, string16* val) const - { - const void* data = ColumnBlob(col); - size_t len = ColumnByteLength(col) / sizeof(char16); - val->resize(len); - if (val->size() != len) - return false; - val->assign(reinterpret_cast(data), len); - return true; - }*/ - - /*bool Statement::ColumnBlobAsVector(int col, std::vector* val) const - { - val->clear(); - - const void* data = sqlite3_column_blob(GetStatement(), col); - int len = sqlite3_column_bytes(GetStatement(), col); - if (data && len > 0) { - val->resize(len); - memcpy(&(*val)[0], data, len); - } - return true; - }*/ - - /*bool Statement::ColumnBlobAsVector( - int col, - std::vector* val) const - { - return ColumnBlobAsVector(col, reinterpret_cast< std::vector* >(val)); - }*/ - - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/Statement.h --- a/Core/SQLite/Statement.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,174 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -#include "NonCopyable.h" -#include "OrthancSQLiteException.h" -#include "StatementId.h" -#include "StatementReference.h" - -#include -#include - -#if ORTHANC_BUILD_UNIT_TESTS == 1 -# include -#endif - - -namespace Orthanc -{ - namespace SQLite - { - class Connection; - - // Possible return values from ColumnType in a statement. These - // should match the values in sqlite3.h. - enum ColumnType - { - COLUMN_TYPE_INTEGER = 1, - COLUMN_TYPE_FLOAT = 2, - COLUMN_TYPE_TEXT = 3, - COLUMN_TYPE_BLOB = 4, - COLUMN_TYPE_NULL = 5 - }; - - class ORTHANC_PUBLIC Statement : public NonCopyable - { - friend class Connection; - -#if ORTHANC_BUILD_UNIT_TESTS == 1 - FRIEND_TEST(SQLStatementTest, Run); - FRIEND_TEST(SQLStatementTest, Reset); -#endif - - private: - StatementReference reference_; - - int CheckError(int err, - ErrorCode code) const; - - void CheckOk(int err, - ErrorCode code) const; - - struct sqlite3_stmt* GetStatement() const - { - return reference_.GetWrappedObject(); - } - - public: - Statement(Connection& database, - const std::string& sql); - - Statement(Connection& database, - const StatementId& id, - const std::string& sql); - - Statement(Connection& database, - const char* sql); - - Statement(Connection& database, - const StatementId& id, - const char* sql); - - ~Statement() - { - Reset(); - } - - bool Run(); - - bool Step(); - - // Diagnostics -------------------------------------------------------------- - - std::string GetOriginalSQLStatement(); - - - // Binding ------------------------------------------------------------------- - - // These all take a 0-based argument index - void BindNull(int col); - void BindBool(int col, bool val); - void BindInt(int col, int val); - void BindInt64(int col, int64_t val); - void BindDouble(int col, double val); - void BindCString(int col, const char* val); - void BindString(int col, const std::string& val); - //void BindString16(int col, const string16& value); - void BindBlob(int col, const void* value, int value_len); - - - // Retrieving ---------------------------------------------------------------- - - // Returns the number of output columns in the result. - int ColumnCount() const; - - // Returns the type associated with the given column. - // - // Watch out: the type may be undefined if you've done something to cause a - // "type conversion." This means requesting the value of a column of a type - // where that type is not the native type. For safety, call ColumnType only - // on a column before getting the value out in any way. - ColumnType GetColumnType(int col) const; - ColumnType GetDeclaredColumnType(int col) const; - - // These all take a 0-based argument index. - bool ColumnIsNull(int col) const ; - bool ColumnBool(int col) const; - int ColumnInt(int col) const; - int64_t ColumnInt64(int col) const; - double ColumnDouble(int col) const; - std::string ColumnString(int col) const; - //string16 ColumnString16(int col) const; - - // When reading a blob, you can get a raw pointer to the underlying data, - // along with the length, or you can just ask us to copy the blob into a - // vector. Danger! ColumnBlob may return NULL if there is no data! - int ColumnByteLength(int col) const; - const void* ColumnBlob(int col) const; - bool ColumnBlobAsString(int col, std::string* blob); - //bool ColumnBlobAsString16(int col, string16* val) const; - //bool ColumnBlobAsVector(int col, std::vector* val) const; - //bool ColumnBlobAsVector(int col, std::vector* val) const; - - // Resets the statement to its initial condition. This includes any current - // result row, and also the bound variables if the |clear_bound_vars| is true. - void Reset(bool clear_bound_vars = true); - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/StatementId.cpp --- a/Core/SQLite/StatementId.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#if ORTHANC_SQLITE_STANDALONE != 1 -#include "../PrecompiledHeaders.h" -#endif - -#include "StatementId.h" - -#include - -namespace Orthanc -{ - namespace SQLite - { - bool StatementId::operator< (const StatementId& other) const - { - if (line_ != other.line_) - return line_ < other.line_; - - return strcmp(file_, other.file_) < 0; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/StatementId.h --- a/Core/SQLite/StatementId.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,66 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -#if ORTHANC_SQLITE_STANDALONE == 1 -# define ORTHANC_PUBLIC -#else -# include "../OrthancFramework.h" -#endif - -namespace Orthanc -{ - namespace SQLite - { - class ORTHANC_PUBLIC StatementId - { - private: - const char* file_; - int line_; - - StatementId(); // Forbidden - - public: - StatementId(const char* file, int line) : file_(file), line_(line) - { - } - - bool operator< (const StatementId& other) const; - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/StatementReference.cpp --- a/Core/SQLite/StatementReference.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#if ORTHANC_SQLITE_STANDALONE != 1 -#include "../PrecompiledHeaders.h" -#endif - -#include "StatementReference.h" -#include "OrthancSQLiteException.h" - -#if ORTHANC_SQLITE_STANDALONE != 1 -#include "../Logging.h" -#endif - -#include -#include -#include "sqlite3.h" - -namespace Orthanc -{ - namespace SQLite - { - bool StatementReference::IsRoot() const - { - return root_ == NULL; - } - - StatementReference::StatementReference() - { - root_ = NULL; - refCount_ = 0; - statement_ = NULL; - assert(IsRoot()); - } - - StatementReference::StatementReference(sqlite3* database, - const char* sql) - { - if (database == NULL || sql == NULL) - { - throw OrthancSQLiteException(ErrorCode_ParameterOutOfRange); - } - - root_ = NULL; - refCount_ = 0; - - int error = sqlite3_prepare_v2(database, sql, -1, &statement_, NULL); - if (error != SQLITE_OK) - { -#if ORTHANC_SQLITE_STANDALONE != 1 - int extended = sqlite3_extended_errcode(database); - LOG(ERROR) << "SQLite: " << sqlite3_errmsg(database) << " (" << extended << ")"; - if (extended == SQLITE_IOERR_SHMSIZE /* 4874 */) - { - LOG(ERROR) << " This probably indicates that your filesystem is full"; - } -#endif - - throw OrthancSQLiteException(ErrorCode_SQLitePrepareStatement); - } - - assert(IsRoot()); - } - - StatementReference::StatementReference(StatementReference& other) - { - refCount_ = 0; - - if (other.IsRoot()) - { - root_ = &other; - } - else - { - root_ = other.root_; - } - - root_->refCount_++; - statement_ = root_->statement_; - - assert(!IsRoot()); - } - - StatementReference::~StatementReference() - { - if (IsRoot()) - { - if (refCount_ != 0) - { - // There remain references to this object. We cannot throw - // an exception because: - // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html - -#if ORTHANC_SQLITE_STANDALONE != 1 - LOG(ERROR) << "Bad value of the reference counter"; -#endif - } - else if (statement_ != NULL) - { - sqlite3_finalize(statement_); - } - } - else - { - if (root_->refCount_ == 0) - { - // There remain references to this object. We cannot throw - // an exception because: - // http://www.parashift.com/c++-faq/dtors-shouldnt-throw.html - -#if ORTHANC_SQLITE_STANDALONE != 1 - LOG(ERROR) << "Bad value of the reference counter"; -#endif - } - else - { - root_->refCount_--; - } - } - } - - uint32_t StatementReference::GetReferenceCount() const - { - return refCount_; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/StatementReference.h --- a/Core/SQLite/StatementReference.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -#if ORTHANC_SQLITE_STANDALONE == 1 -# define ORTHANC_PUBLIC -#else -# include "../OrthancFramework.h" -#endif - -#include "NonCopyable.h" -#include "SQLiteTypes.h" - -#include -#include -#include - - -namespace Orthanc -{ - namespace SQLite - { - class ORTHANC_PUBLIC StatementReference : NonCopyable - { - private: - StatementReference* root_; // Only used for non-root nodes - uint32_t refCount_; // Only used for root node - struct sqlite3_stmt* statement_; - - bool IsRoot() const; - - public: - StatementReference(); - - StatementReference(sqlite3* database, - const char* sql); - - StatementReference(StatementReference& other); - - ~StatementReference(); - - uint32_t GetReferenceCount() const; - - struct sqlite3_stmt* GetWrappedObject() const - { - assert(statement_ != NULL); - return statement_; - } - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/Transaction.cpp --- a/Core/SQLite/Transaction.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#if ORTHANC_SQLITE_STANDALONE != 1 -#include "../PrecompiledHeaders.h" -#endif - -#include "Transaction.h" -#include "OrthancSQLiteException.h" - -namespace Orthanc -{ - namespace SQLite - { - Transaction::Transaction(Connection& connection) : - connection_(connection), - isOpen_(false) - { - } - - Transaction::~Transaction() - { - if (isOpen_) - { - connection_.RollbackTransaction(); - } - } - - void Transaction::Begin() - { - if (isOpen_) - { - throw OrthancSQLiteException(ErrorCode_SQLiteTransactionAlreadyStarted); - } - - isOpen_ = connection_.BeginTransaction(); - if (!isOpen_) - { - throw OrthancSQLiteException(ErrorCode_SQLiteTransactionBegin); - } - } - - void Transaction::Rollback() - { - if (!isOpen_) - { - throw OrthancSQLiteException(ErrorCode_SQLiteRollbackWithoutTransaction); - } - - isOpen_ = false; - - connection_.RollbackTransaction(); - } - - void Transaction::Commit() - { - if (!isOpen_) - { - throw OrthancSQLiteException(ErrorCode_SQLiteRollbackWithoutTransaction); - } - - isOpen_ = false; - - if (!connection_.CommitTransaction()) - { - throw OrthancSQLiteException(ErrorCode_SQLiteTransactionCommit); - } - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SQLite/Transaction.h --- a/Core/SQLite/Transaction.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * - * Copyright (C) 2012-2016 Sebastien Jodogne , - * Medical Physics Department, CHU of Liege, Belgium - * - * Copyright (c) 2012 The Chromium Authors. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google Inc., the name of the CHU of Liege, - * nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ - - -#pragma once - -#include "Connection.h" -#include "ITransaction.h" - -namespace Orthanc -{ - namespace SQLite - { - class ORTHANC_PUBLIC Transaction : public ITransaction - { - private: - Connection& connection_; - - // True when the transaction is open, false when it's already been committed - // or rolled back. - bool isOpen_; - - public: - explicit Transaction(Connection& connection); - - virtual ~Transaction(); - - // Returns true when there is a transaction that has been successfully begun. - bool IsOpen() const { return isOpen_; } - - virtual void Begin(); - - virtual void Rollback(); - - virtual void Commit(); - }; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SerializationToolbox.cpp --- a/Core/SerializationToolbox.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,444 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "SerializationToolbox.h" - -#include "OrthancException.h" - -#if ORTHANC_ENABLE_DCMTK == 1 -# include "DicomParsing/FromDcmtkBridge.h" -#endif - -namespace Orthanc -{ - static bool ParseTagInternal(DicomTag& tag, - const char* name) - { -#if ORTHANC_ENABLE_DCMTK == 1 - try - { - tag = FromDcmtkBridge::ParseTag(name); - return true; - } - catch (OrthancException&) - { - return false; - } -#else - return DicomTag::ParseHexadecimal(tag, name); -#endif - } - - - std::string SerializationToolbox::ReadString(const Json::Value& value, - const std::string& field) - { - if (value.type() != Json::objectValue || - !value.isMember(field.c_str()) || - value[field.c_str()].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat, - "String value expected in field: " + field); - } - else - { - return value[field.c_str()].asString(); - } - } - - - int SerializationToolbox::ReadInteger(const Json::Value& value, - const std::string& field) - { - if (value.type() != Json::objectValue || - !value.isMember(field.c_str()) || - (value[field.c_str()].type() != Json::intValue && - value[field.c_str()].type() != Json::uintValue)) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Integer value expected in field: " + field); - } - else - { - return value[field.c_str()].asInt(); - } - } - - - int SerializationToolbox::ReadInteger(const Json::Value& value, - const std::string& field, - int defaultValue) - { - if (value.isMember(field.c_str())) - { - return ReadInteger(value, field); - } - else - { - return defaultValue; - } - } - - - unsigned int SerializationToolbox::ReadUnsignedInteger(const Json::Value& value, - const std::string& field) - { - int tmp = ReadInteger(value, field); - - if (tmp < 0) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Unsigned integer value expected in field: " + field); - } - else - { - return static_cast(tmp); - } - } - - - bool SerializationToolbox::ReadBoolean(const Json::Value& value, - const std::string& field) - { - if (value.type() != Json::objectValue || - !value.isMember(field.c_str()) || - value[field.c_str()].type() != Json::booleanValue) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Boolean value expected in field: " + field); - } - else - { - return value[field.c_str()].asBool(); - } - } - - - void SerializationToolbox::ReadArrayOfStrings(std::vector& target, - const Json::Value& value, - const std::string& field) - { - if (value.type() != Json::objectValue || - !value.isMember(field.c_str()) || - value[field.c_str()].type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_BadFileFormat, - "List of strings expected in field: " + field); - } - - const Json::Value& arr = value[field.c_str()]; - - target.resize(arr.size()); - - for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++) - { - if (arr[i].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat, - "List of strings expected in field: " + field); - } - else - { - target[i] = arr[i].asString(); - } - } - } - - - void SerializationToolbox::ReadListOfStrings(std::list& target, - const Json::Value& value, - const std::string& field) - { - std::vector tmp; - ReadArrayOfStrings(tmp, value, field); - - target.clear(); - for (size_t i = 0; i < tmp.size(); i++) - { - target.push_back(tmp[i]); - } - } - - - void SerializationToolbox::ReadSetOfStrings(std::set& target, - const Json::Value& value, - const std::string& field) - { - std::vector tmp; - ReadArrayOfStrings(tmp, value, field); - - target.clear(); - for (size_t i = 0; i < tmp.size(); i++) - { - target.insert(tmp[i]); - } - } - - - void SerializationToolbox::ReadSetOfTags(std::set& target, - const Json::Value& value, - const std::string& field) - { - if (value.type() != Json::objectValue || - !value.isMember(field.c_str()) || - value[field.c_str()].type() != Json::arrayValue) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Set of DICOM tags expected in field: " + field); - } - - const Json::Value& arr = value[field.c_str()]; - - target.clear(); - - for (Json::Value::ArrayIndex i = 0; i < arr.size(); i++) - { - DicomTag tag(0, 0); - - if (arr[i].type() != Json::stringValue || - !ParseTagInternal(tag, arr[i].asCString())) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Set of DICOM tags expected in field: " + field); - } - else - { - target.insert(tag); - } - } - } - - - void SerializationToolbox::ReadMapOfStrings(std::map& target, - const Json::Value& value, - const std::string& field) - { - if (value.type() != Json::objectValue || - !value.isMember(field.c_str()) || - value[field.c_str()].type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Associative array of strings to strings expected in field: " + field); - } - - const Json::Value& source = value[field.c_str()]; - - target.clear(); - - Json::Value::Members members = source.getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& tmp = source[members[i]]; - - if (tmp.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Associative array of string to strings expected in field: " + field); - } - else - { - target[members[i]] = tmp.asString(); - } - } - } - - - void SerializationToolbox::ReadMapOfTags(std::map& target, - const Json::Value& value, - const std::string& field) - { - if (value.type() != Json::objectValue || - !value.isMember(field.c_str()) || - value[field.c_str()].type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Associative array of DICOM tags to strings expected in field: " + field); - } - - const Json::Value& source = value[field.c_str()]; - - target.clear(); - - Json::Value::Members members = source.getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - const Json::Value& tmp = source[members[i]]; - - DicomTag tag(0, 0); - - if (!ParseTagInternal(tag, members[i].c_str()) || - tmp.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat, - "Associative array of DICOM tags to strings expected in field: " + field); - } - else - { - target[tag] = tmp.asString(); - } - } - } - - - void SerializationToolbox::WriteArrayOfStrings(Json::Value& target, - const std::vector& values, - const std::string& field) - { - if (target.type() != Json::objectValue || - target.isMember(field.c_str())) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value& value = target[field]; - - value = Json::arrayValue; - for (size_t i = 0; i < values.size(); i++) - { - value.append(values[i]); - } - } - - - void SerializationToolbox::WriteListOfStrings(Json::Value& target, - const std::list& values, - const std::string& field) - { - if (target.type() != Json::objectValue || - target.isMember(field.c_str())) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value& value = target[field]; - - value = Json::arrayValue; - - for (std::list::const_iterator it = values.begin(); - it != values.end(); ++it) - { - value.append(*it); - } - } - - - void SerializationToolbox::WriteSetOfStrings(Json::Value& target, - const std::set& values, - const std::string& field) - { - if (target.type() != Json::objectValue || - target.isMember(field.c_str())) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value& value = target[field]; - - value = Json::arrayValue; - - for (std::set::const_iterator it = values.begin(); - it != values.end(); ++it) - { - value.append(*it); - } - } - - - void SerializationToolbox::WriteSetOfTags(Json::Value& target, - const std::set& tags, - const std::string& field) - { - if (target.type() != Json::objectValue || - target.isMember(field.c_str())) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value& value = target[field]; - - value = Json::arrayValue; - - for (std::set::const_iterator it = tags.begin(); - it != tags.end(); ++it) - { - value.append(it->Format()); - } - } - - - void SerializationToolbox::WriteMapOfStrings(Json::Value& target, - const std::map& values, - const std::string& field) - { - if (target.type() != Json::objectValue || - target.isMember(field.c_str())) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value& value = target[field]; - - value = Json::objectValue; - - for (std::map::const_iterator - it = values.begin(); it != values.end(); ++it) - { - value[it->first] = it->second; - } - } - - - void SerializationToolbox::WriteMapOfTags(Json::Value& target, - const std::map& values, - const std::string& field) - { - if (target.type() != Json::objectValue || - target.isMember(field.c_str())) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - Json::Value& value = target[field]; - - value = Json::objectValue; - - for (std::map::const_iterator - it = values.begin(); it != values.end(); ++it) - { - value[it->first.Format()] = it->second; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SerializationToolbox.h --- a/Core/SerializationToolbox.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "DicomFormat/DicomTag.h" -#include "OrthancFramework.h" - -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC SerializationToolbox - { - public: - static std::string ReadString(const Json::Value& value, - const std::string& field); - - static int ReadInteger(const Json::Value& value, - const std::string& field); - - static int ReadInteger(const Json::Value& value, - const std::string& field, - int defaultValue); - - static unsigned int ReadUnsignedInteger(const Json::Value& value, - const std::string& field); - - static bool ReadBoolean(const Json::Value& value, - const std::string& field); - - static void ReadArrayOfStrings(std::vector& target, - const Json::Value& value, - const std::string& field); - - static void ReadListOfStrings(std::list& target, - const Json::Value& value, - const std::string& field); - - static void ReadSetOfStrings(std::set& target, - const Json::Value& value, - const std::string& field); - - static void ReadSetOfTags(std::set& target, - const Json::Value& value, - const std::string& field); - - static void ReadMapOfStrings(std::map& values, - const Json::Value& target, - const std::string& field); - - static void ReadMapOfTags(std::map& values, - const Json::Value& target, - const std::string& field); - - static void WriteArrayOfStrings(Json::Value& target, - const std::vector& values, - const std::string& field); - - static void WriteListOfStrings(Json::Value& target, - const std::list& values, - const std::string& field); - - static void WriteSetOfStrings(Json::Value& target, - const std::set& values, - const std::string& field); - - static void WriteSetOfTags(Json::Value& target, - const std::set& tags, - const std::string& field); - - static void WriteMapOfStrings(Json::Value& target, - const std::map& values, - const std::string& field); - - static void WriteMapOfTags(Json::Value& target, - const std::map& values, - const std::string& field); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SharedLibrary.cpp --- a/Core/SharedLibrary.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,154 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "SharedLibrary.h" - -#include "Logging.h" -#include "OrthancException.h" - -#include - -#if defined(_WIN32) -#include -#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) -#include -#else -#error Support your platform here -#endif - -namespace Orthanc -{ - SharedLibrary::SharedLibrary(const std::string& path) : - path_(path), - handle_(NULL) - { -#if defined(_WIN32) - handle_ = ::LoadLibraryA(path_.c_str()); - if (handle_ == NULL) - { - LOG(ERROR) << "LoadLibrary(" << path_ << ") failed: Error " << ::GetLastError(); - throw OrthancException(ErrorCode_SharedLibrary); - } - -#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) - - /** - * "RTLD_LOCAL" is the default, and is only present to be - * explicit. "RTLD_DEEPBIND" was added in Orthanc 1.6.0, in order - * to avoid crashes while loading plugins from the LSB binaries of - * the Orthanc core. - * - * BUT this had no effect, and this results in a crash if loading - * the Python 2.7 plugin => We disabled it again in Orthanc 1.6.1. - **/ - -#if 0 // && defined(RTLD_DEEPBIND) // This is a GNU extension - // Disabled in Orthanc 1.6.1 - handle_ = ::dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND); -#else - handle_ = ::dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL); -#endif - - if (handle_ == NULL) - { - std::string explanation; - const char *tmp = ::dlerror(); - if (tmp) - { - explanation = ": Error " + std::string(tmp); - } - - LOG(ERROR) << "dlopen(" << path_ << ") failed" << explanation; - throw OrthancException(ErrorCode_SharedLibrary); - } - -#else -#error Support your platform here -#endif - } - - SharedLibrary::~SharedLibrary() - { - if (handle_) - { -#if defined(_WIN32) - ::FreeLibrary((HMODULE)handle_); -#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) - ::dlclose(handle_); -#else -#error Support your platform here -#endif - } - } - - - SharedLibrary::FunctionPointer SharedLibrary::GetFunctionInternal(const std::string& name) - { - if (!handle_) - { - throw OrthancException(ErrorCode_InternalError); - } - -#if defined(_WIN32) - return ::GetProcAddress((HMODULE)handle_, name.c_str()); -#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) - return ::dlsym(handle_, name.c_str()); -#else -#error Support your platform here -#endif - } - - - SharedLibrary::FunctionPointer SharedLibrary::GetFunction(const std::string& name) - { - SharedLibrary::FunctionPointer result = GetFunctionInternal(name); - - if (result == NULL) - { - throw OrthancException( - ErrorCode_SharedLibrary, - "Shared library does not expose function \"" + name + "\""); - } - else - { - return result; - } - } - - - bool SharedLibrary::HasFunction(const std::string& name) - { - return GetFunctionInternal(name) != NULL; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SharedLibrary.h --- a/Core/SharedLibrary.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "OrthancFramework.h" - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The namespace SystemToolbox cannot be used in sandboxed environments -#endif - -#if defined(_WIN32) -#include -#endif - -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC SharedLibrary : public boost::noncopyable - { - public: -#if defined(_WIN32) - typedef FARPROC FunctionPointer; -#else - typedef void* FunctionPointer; -#endif - - private: - std::string path_; - void *handle_; - - FunctionPointer GetFunctionInternal(const std::string& name); - - public: - explicit SharedLibrary(const std::string& path); - - ~SharedLibrary(); - - const std::string& GetPath() const - { - return path_; - } - - bool HasFunction(const std::string& name); - - FunctionPointer GetFunction(const std::string& name); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SystemToolbox.cpp --- a/Core/SystemToolbox.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,764 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "SystemToolbox.h" - - -#if defined(_WIN32) -# include -# include // For "_spawnvp()" and "_getpid()" -# include // For "environ" -#else -# include // For "execvp()" -# include // For "waitpid()" -#endif - - -#if defined(__APPLE__) && defined(__MACH__) -# include /* _NSGetExecutablePath */ -# include /* PATH_MAX */ -#endif - - -#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) -# include /* PATH_MAX */ -# include -# include -#endif - - -#if defined(__OpenBSD__) -# include // For "sysctl", "CTL_KERN" and "KERN_PROC_ARGS" -#endif - - -#include "Logging.h" -#include "OrthancException.h" -#include "Toolbox.h" - -#include -#include -#include -#include - - -/*========================================================================= - The section below comes from the Boost 1.68.0 project: - https://github.com/boostorg/program_options/blob/boost-1.68.0/src/parsers.cpp - - Copyright Vladimir Prus 2002-2004. - Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt - or copy at http://www.boost.org/LICENSE_1_0.txt) - =========================================================================*/ - -// The 'environ' should be declared in some cases. E.g. Linux man page says: -// (This variable must be declared in the user program, but is declared in -// the header file unistd.h in case the header files came from libc4 or libc5, -// and in case they came from glibc and _GNU_SOURCE was defined.) -// To be safe, declare it here. - -// It appears that on Mac OS X the 'environ' variable is not -// available to dynamically linked libraries. -// See: http://article.gmane.org/gmane.comp.lib.boost.devel/103843 -// See: http://lists.gnu.org/archive/html/bug-guile/2004-01/msg00013.html -#if defined(__APPLE__) && defined(__DYNAMIC__) -// The proper include for this is crt_externs.h, however it's not -// available on iOS. The right replacement is not known. See -// https://svn.boost.org/trac/boost/ticket/5053 -extern "C" -{ - extern char ***_NSGetEnviron(void); -} -# define environ (*_NSGetEnviron()) -#else -# if defined(__MWERKS__) -# include -# else -# if !defined(_WIN32) || defined(__COMO_VERSION__) -extern char** environ; -# endif -# endif -#endif - - -/*========================================================================= - End of section from the Boost 1.68.0 project - =========================================================================*/ - - -namespace Orthanc -{ - static bool finish_; - static ServerBarrierEvent barrierEvent_; - -#if defined(_WIN32) - static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType) - { - // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx - finish_ = true; - return true; - } -#else - static void SignalHandler(int signal) - { - if (signal == SIGHUP) - { - barrierEvent_ = ServerBarrierEvent_Reload; - } - - finish_ = true; - } -#endif - - - static ServerBarrierEvent ServerBarrierInternal(const bool* stopFlag) - { -#if defined(_WIN32) - SetConsoleCtrlHandler(ConsoleControlHandler, true); -#else - signal(SIGINT, SignalHandler); - signal(SIGQUIT, SignalHandler); - signal(SIGTERM, SignalHandler); - signal(SIGHUP, SignalHandler); -#endif - - // Active loop that awakens every 100ms - finish_ = false; - barrierEvent_ = ServerBarrierEvent_Stop; - while (!(*stopFlag || finish_)) - { - SystemToolbox::USleep(100 * 1000); - } - -#if defined(_WIN32) - SetConsoleCtrlHandler(ConsoleControlHandler, false); -#else - signal(SIGINT, NULL); - signal(SIGQUIT, NULL); - signal(SIGTERM, NULL); - signal(SIGHUP, NULL); -#endif - - return barrierEvent_; - } - - - ServerBarrierEvent SystemToolbox::ServerBarrier(const bool& stopFlag) - { - return ServerBarrierInternal(&stopFlag); - } - - - ServerBarrierEvent SystemToolbox::ServerBarrier() - { - const bool stopFlag = false; - return ServerBarrierInternal(&stopFlag); - } - - - void SystemToolbox::USleep(uint64_t microSeconds) - { -#if defined(_WIN32) - ::Sleep(static_cast(microSeconds / static_cast(1000))); -#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__native_client__) - usleep(microSeconds); -#else -#error Support your platform here -#endif - } - - - static std::streamsize GetStreamSize(std::istream& f) - { - // http://www.cplusplus.com/reference/iostream/istream/tellg/ - f.seekg(0, std::ios::end); - std::streamsize size = f.tellg(); - f.seekg(0, std::ios::beg); - - return size; - } - - - void SystemToolbox::ReadFile(std::string& content, - const std::string& path, - bool log) - { - if (!IsRegularFile(path)) - { - throw OrthancException(ErrorCode_RegularFileExpected, - "The path does not point to a regular file: " + path, - log); - } - - boost::filesystem::ifstream f; - f.open(path, std::ifstream::in | std::ifstream::binary); - if (!f.good()) - { - throw OrthancException(ErrorCode_InexistentFile, - "File not found: " + path, - log); - } - - std::streamsize size = GetStreamSize(f); - content.resize(static_cast(size)); - if (size != 0) - { - f.read(&content[0], size); - } - - f.close(); - } - - - bool SystemToolbox::ReadHeader(std::string& header, - const std::string& path, - size_t headerSize) - { - if (!IsRegularFile(path)) - { - throw OrthancException(ErrorCode_RegularFileExpected, - "The path does not point to a regular file: " + path); - } - - boost::filesystem::ifstream f; - f.open(path, std::ifstream::in | std::ifstream::binary); - if (!f.good()) - { - throw OrthancException(ErrorCode_InexistentFile); - } - - bool full = true; - - { - std::streamsize size = GetStreamSize(f); - if (size <= 0) - { - headerSize = 0; - full = false; - } - else if (static_cast(size) < headerSize) - { - headerSize = static_cast(size); // Truncate to the size of the file - full = false; - } - } - - header.resize(headerSize); - if (headerSize != 0) - { - f.read(&header[0], headerSize); - } - - f.close(); - - return full; - } - - - void SystemToolbox::WriteFile(const void* content, - size_t size, - const std::string& path) - { - boost::filesystem::ofstream f; - f.open(path, std::ofstream::out | std::ofstream::binary); - if (!f.good()) - { - throw OrthancException(ErrorCode_CannotWriteFile); - } - - if (size != 0) - { - f.write(reinterpret_cast(content), size); - - if (!f.good()) - { - f.close(); - throw OrthancException(ErrorCode_FileStorageCannotWrite); - } - } - - f.close(); - } - - - void SystemToolbox::WriteFile(const std::string& content, - const std::string& path) - { - WriteFile(content.size() > 0 ? content.c_str() : NULL, - content.size(), path); - } - - - void SystemToolbox::RemoveFile(const std::string& path) - { - if (boost::filesystem::exists(path)) - { - if (IsRegularFile(path)) - { - boost::filesystem::remove(path); - } - else - { - throw OrthancException(ErrorCode_RegularFileExpected); - } - } - } - - - uint64_t SystemToolbox::GetFileSize(const std::string& path) - { - try - { - return static_cast(boost::filesystem::file_size(path)); - } - catch (boost::filesystem::filesystem_error&) - { - throw OrthancException(ErrorCode_InexistentFile); - } - } - - - void SystemToolbox::MakeDirectory(const std::string& path) - { - if (boost::filesystem::exists(path)) - { - if (!boost::filesystem::is_directory(path)) - { - throw OrthancException(ErrorCode_DirectoryOverFile); - } - } - else - { - if (!boost::filesystem::create_directories(path)) - { - throw OrthancException(ErrorCode_MakeDirectory); - } - } - } - - - bool SystemToolbox::IsExistingFile(const std::string& path) - { - return boost::filesystem::exists(path); - } - - -#if defined(_WIN32) - static std::string GetPathToExecutableInternal() - { - // Yes, this is ugly, but there is no simple way to get the - // required buffer size, so we use a big constant - std::vector buffer(32768); - /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast(buffer.size() - 1)); - return std::string(&buffer[0]); - } - -#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) - static std::string GetPathToExecutableInternal() - { - // NOTE: For FreeBSD, using KERN_PROC_PATHNAME might be a better alternative - - std::vector buffer(PATH_MAX + 1); - ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1); - if (bytes == 0) - { - throw OrthancException(ErrorCode_PathToExecutable); - } - - return std::string(&buffer[0]); - } - -#elif defined(__APPLE__) && defined(__MACH__) - static std::string GetPathToExecutableInternal() - { - char pathbuf[PATH_MAX + 1]; - unsigned int bufsize = static_cast(sizeof(pathbuf)); - - _NSGetExecutablePath( pathbuf, &bufsize); - - return std::string(pathbuf); - } - -#elif defined(__OpenBSD__) - static std::string GetPathToExecutableInternal() - { - // This is an adapted version of the patch proposed in issue #64 - // without an explicit call to "malloc()" to prevent memory leak - // https://bitbucket.org/sjodogne/orthanc/issues/64/add-openbsd-support - // https://stackoverflow.com/q/31494901/881731 - - const int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; - - size_t len; - if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1) - { - throw OrthancException(ErrorCode_PathToExecutable); - } - - std::string tmp; - tmp.resize(len); - - char** buffer = reinterpret_cast(&tmp[0]); - - if (sysctl(mib, 4, buffer, &len, NULL, 0) == -1) - { - throw OrthancException(ErrorCode_PathToExecutable); - } - else - { - return std::string(buffer[0]); - } - } - -#else -#error Support your platform here -#endif - - - std::string SystemToolbox::GetPathToExecutable() - { - boost::filesystem::path p(GetPathToExecutableInternal()); - return boost::filesystem::absolute(p).string(); - } - - - std::string SystemToolbox::GetDirectoryOfExecutable() - { - boost::filesystem::path p(GetPathToExecutableInternal()); - return boost::filesystem::absolute(p.parent_path()).string(); - } - - - void SystemToolbox::ExecuteSystemCommand(const std::string& command, - const std::vector& arguments) - { - // Convert the arguments as a C array - std::vector args(arguments.size() + 2); - - args.front() = const_cast(command.c_str()); - - for (size_t i = 0; i < arguments.size(); i++) - { - args[i + 1] = const_cast(arguments[i].c_str()); - } - - args.back() = NULL; - - int status; - -#if defined(_WIN32) - // http://msdn.microsoft.com/en-us/library/275khfab.aspx - status = static_cast(_spawnvp(_P_OVERLAY, command.c_str(), &args[0])); - -#else - int pid = fork(); - - if (pid == -1) - { - // Error in fork() - throw OrthancException(ErrorCode_SystemCommand, "Cannot fork a child process"); - } - else if (pid == 0) - { - // Execute the system command in the child process - execvp(command.c_str(), &args[0]); - - // We should never get here - _exit(1); - } - else - { - // Wait for the system command to exit - waitpid(pid, &status, 0); - } -#endif - - if (status != 0) - { - throw OrthancException(ErrorCode_SystemCommand, - "System command failed with status code " + - boost::lexical_cast(status)); - } - } - - - int SystemToolbox::GetProcessId() - { -#if defined(_WIN32) - return static_cast(_getpid()); -#else - return static_cast(getpid()); -#endif - } - - - bool SystemToolbox::IsRegularFile(const std::string& path) - { - namespace fs = boost::filesystem; - - try - { - if (fs::exists(path)) - { - fs::file_status status = fs::status(path); - return (status.type() == boost::filesystem::regular_file || - status.type() == boost::filesystem::reparse_file); // Fix BitBucket issue #11 - } - } - catch (fs::filesystem_error&) - { - } - - return false; - } - - - FILE* SystemToolbox::OpenFile(const std::string& path, - FileMode mode) - { -#if defined(_WIN32) - // TODO Deal with special characters by converting to the current locale -#endif - - const char* m; - switch (mode) - { - case FileMode_ReadBinary: - m = "rb"; - break; - - case FileMode_WriteBinary: - m = "wb"; - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - return fopen(path.c_str(), m); - } - - - static boost::posix_time::ptime GetNow(bool utc) - { - if (utc) - { - return boost::posix_time::second_clock::universal_time(); - } - else - { - return boost::posix_time::second_clock::local_time(); - } - } - - - std::string SystemToolbox::GetNowIsoString(bool utc) - { - return boost::posix_time::to_iso_string(GetNow(utc)); - } - - - void SystemToolbox::GetNowDicom(std::string& date, - std::string& time, - bool utc) - { - boost::posix_time::ptime now = GetNow(utc); - tm tm = boost::posix_time::to_tm(now); - - char s[32]; - sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); - date.assign(s); - - // TODO milliseconds - sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0); - time.assign(s); - } - - - unsigned int SystemToolbox::GetHardwareConcurrency() - { - // Get the number of available hardware threads (e.g. number of - // CPUs or cores or hyperthreading units) - unsigned int threads = boost::thread::hardware_concurrency(); - - if (threads <= 0) - { - return 1; - } - else - { - return threads; - } - } - - - MimeType SystemToolbox::AutodetectMimeType(const std::string& path) - { - std::string extension = boost::filesystem::extension(path); - Toolbox::ToLowerCase(extension); - - // http://en.wikipedia.org/wiki/Mime_types - // Text types - if (extension == ".txt") - { - return MimeType_PlainText; - } - else if (extension == ".html") - { - return MimeType_Html; - } - else if (extension == ".xml") - { - return MimeType_Xml; - } - else if (extension == ".css") - { - return MimeType_Css; - } - - // Application types - else if (extension == ".js") - { - return MimeType_JavaScript; - } - else if (extension == ".json" || - extension == ".nmf" /* manifest */) - { - return MimeType_Json; - } - else if (extension == ".pdf") - { - return MimeType_Pdf; - } - else if (extension == ".wasm") - { - return MimeType_WebAssembly; - } - else if (extension == ".nexe") - { - return MimeType_NaCl; - } - else if (extension == ".pexe") - { - return MimeType_PNaCl; - } - - // Images types - else if (extension == ".jpg" || - extension == ".jpeg") - { - return MimeType_Jpeg; - } - else if (extension == ".gif") - { - return MimeType_Gif; - } - else if (extension == ".png") - { - return MimeType_Png; - } - else if (extension == ".pam") - { - return MimeType_Pam; - } - else if (extension == ".svg") - { - return MimeType_Svg; - } - - // Various types - else if (extension == ".woff") - { - return MimeType_Woff; - } - else if (extension == ".woff2") - { - return MimeType_Woff2; - } - - // Default type - else - { - LOG(INFO) << "Unknown MIME type for extension \"" << extension << "\""; - return MimeType_Binary; - } - } - - - void SystemToolbox::GetEnvironmentVariables(std::map& env) - { - env.clear(); - - for (char **p = environ; *p != NULL; p++) - { - std::string v(*p); - size_t pos = v.find('='); - - if (pos != std::string::npos) - { - std::string key = v.substr(0, pos); - std::string value = v.substr(pos + 1); - env[key] = value; - } - } - } - - - std::string SystemToolbox::InterpretRelativePath(const std::string& baseDirectory, - const std::string& relativePath) - { - boost::filesystem::path base(baseDirectory); - boost::filesystem::path relative(relativePath); - - /** - The following lines should be equivalent to this one: - - return (base / relative).string(); - - However, for some unknown reason, some versions of Boost do not - make the proper path resolution when "baseDirectory" is an - absolute path. So, a hack is used below. - **/ - - if (relative.is_absolute()) - { - return relative.string(); - } - else - { - return (base / relative).string(); - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/SystemToolbox.h --- a/Core/SystemToolbox.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The namespace SystemToolbox cannot be used in sandboxed environments -#endif - -#include "Enumerations.h" -#include "OrthancFramework.h" - -#include -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC SystemToolbox - { - public: - static void USleep(uint64_t microSeconds); - - static ServerBarrierEvent ServerBarrier(const bool& stopFlag); - - static ServerBarrierEvent ServerBarrier(); - - static void ReadFile(std::string& content, - const std::string& path, - bool log = true); - - static bool ReadHeader(std::string& header, - const std::string& path, - size_t headerSize); - - static void WriteFile(const void* content, - size_t size, - const std::string& path); - - static void WriteFile(const std::string& content, - const std::string& path); - - static void RemoveFile(const std::string& path); - - static uint64_t GetFileSize(const std::string& path); - - static void MakeDirectory(const std::string& path); - - static bool IsExistingFile(const std::string& path); - - static std::string GetPathToExecutable(); - - static std::string GetDirectoryOfExecutable(); - - static void ExecuteSystemCommand(const std::string& command, - const std::vector& arguments); - - static int GetProcessId(); - - static bool IsRegularFile(const std::string& path); - - static FILE* OpenFile(const std::string& path, - FileMode mode); - - static std::string GetNowIsoString(bool utc); - - static void GetNowDicom(std::string& date, - std::string& time, - bool utc); - - static unsigned int GetHardwareConcurrency(); - - static MimeType AutodetectMimeType(const std::string& path); - - static void GetEnvironmentVariables(std::map& env); - - static std::string InterpretRelativePath(const std::string& baseDirectory, - const std::string& relativePath); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/TemporaryFile.cpp --- a/Core/TemporaryFile.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,140 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "TemporaryFile.h" - -#include "OrthancException.h" -#include "SystemToolbox.h" -#include "Toolbox.h" - -#include - -namespace Orthanc -{ - static std::string CreateTemporaryPath(const char* temporaryDirectory, - const char* extension) - { - boost::filesystem::path dir; - - if (temporaryDirectory == NULL) - { -#if BOOST_HAS_FILESYSTEM_V3 == 1 - dir = boost::filesystem::temp_directory_path(); -#elif defined(__linux__) - dir = "/tmp"; -#else -# error Support your platform here -#endif - } - else - { - dir = temporaryDirectory; - } - - // We use UUID to create unique path to temporary files - const std::string uuid = Orthanc::Toolbox::GenerateUuid(); - - // New in Orthanc 1.5.8: Prefix the process ID to the name of the - // temporary files, in order to locate orphan temporary files that - // were left by instances of Orthanc that exited in non-clean way - // https://groups.google.com/d/msg/orthanc-users/MSJX53bw6Lw/d3S3lRRLAwAJ - std::string filename = "Orthanc-" + boost::lexical_cast(SystemToolbox::GetProcessId()) + "-" + uuid; - - if (extension != NULL) - { - filename.append(extension); - } - - dir /= filename; - return dir.string(); - } - - - TemporaryFile::TemporaryFile() : - path_(CreateTemporaryPath(NULL, NULL)) - { - } - - - TemporaryFile::TemporaryFile(const std::string& temporaryDirectory, - const std::string& extension) : - path_(CreateTemporaryPath(temporaryDirectory.c_str(), extension.c_str())) - { - } - - - TemporaryFile::~TemporaryFile() - { - boost::filesystem::remove(path_); - } - - - void TemporaryFile::Write(const std::string& content) - { - try - { - SystemToolbox::WriteFile(content, path_); - } - catch (OrthancException& e) - { - throw OrthancException(e.GetErrorCode(), - "Can't create temporary file \"" + path_ + - "\" with " + boost::lexical_cast(content.size()) + - " bytes: Check you have write access to the " - "temporary directory and that it is not full"); - } - } - - - void TemporaryFile::Read(std::string& content) const - { - try - { - SystemToolbox::ReadFile(content, path_); - } - catch (OrthancException& e) - { - throw OrthancException(e.GetErrorCode(), - "Can't read temporary file \"" + path_ + - "\": Another process has corrupted the temporary directory"); - } - } - - - void TemporaryFile::Touch() - { - std::string empty; - Write(empty); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/TemporaryFile.h --- a/Core/TemporaryFile.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "OrthancFramework.h" - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#if ORTHANC_SANDBOXED == 1 -# error The class TemporaryFile cannot be used in sandboxed environments -#endif - -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC TemporaryFile : public boost::noncopyable - { - private: - std::string path_; - - public: - TemporaryFile(); - - TemporaryFile(const std::string& temporaryFolder, - const std::string& extension); - - ~TemporaryFile(); - - const std::string& GetPath() const - { - return path_; - } - - void Write(const std::string& content); - - void Read(std::string& content) const; - - void Touch(); - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Toolbox.cpp --- a/Core/Toolbox.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2257 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "Toolbox.h" - -#include "Compatibility.h" -#include "OrthancException.h" -#include "Logging.h" - -#include -#include -#include -#include - -#if BOOST_VERSION >= 106600 -# include -#else -# include -#endif - -#include -#include -#include -#include -#include - - -#if ORTHANC_ENABLE_MD5 == 1 -// TODO - Could be replaced by starting -// with Boost >= 1.66.0 -# include "../Resources/ThirdParty/md5/md5.h" -#endif - -#if ORTHANC_ENABLE_BASE64 == 1 -# include "../Resources/ThirdParty/base64/base64.h" -#endif - -#if ORTHANC_ENABLE_LOCALE == 1 -# include -#endif - -#if ORTHANC_ENABLE_SSL == 1 -// For OpenSSL initialization and finalization -# include -# include -# include -# include -# include -#endif - - -#if defined(_MSC_VER) && (_MSC_VER < 1800) -// Patch for the missing "_strtoll" symbol when compiling with Visual Studio < 2013 -extern "C" -{ - int64_t _strtoi64(const char *nptr, char **endptr, int base); - int64_t strtoll(const char *nptr, char **endptr, int base) - { - return _strtoi64(nptr, endptr, base); - } -} -#endif - - -#if defined(_WIN32) -# include // For ::Sleep -#endif - - -#if ORTHANC_ENABLE_PUGIXML == 1 -# include "ChunkedBuffer.h" -#endif - - -// Inclusions for UUID -// http://stackoverflow.com/a/1626302 - -extern "C" -{ -#if defined(_WIN32) -# include -#else -# include -#endif -} - - -#if defined(ORTHANC_STATIC_ICU) -# if (ORTHANC_STATIC_ICU == 1 && ORTHANC_ENABLE_LOCALE == 1) -# include -# include -# include -# include "Compression/GzipCompressor.h" - -static std::string globalIcuData_; - -extern "C" -{ - // This is dummy content for the "icudt58_dat" (resp. "icudt63_dat") - // global variable from the autogenerated "icudt58l_dat.c" - // (resp. "icudt63l_dat.c") file that contains a huge C array. In - // Orthanc, this array is compressed using gzip and attached as a - // resource, then uncompressed during the launch of Orthanc by - // static function "InitializeIcu()". - struct - { - double bogus; - uint8_t *bytes; - } U_ICUDATA_ENTRY_POINT = { 0.0, NULL }; -} - -# if defined(__LSB_VERSION__) -extern "C" -{ - /** - * The "tzname" global variable is declared as "extern" but is not - * defined in any compilation module, if using Linux Standard Base, - * as soon as OpenSSL or cURL is in use on Ubuntu >= 18.04 (glibc >= - * 2.27). The variable "__tzname" is always properly declared *and* - * defined. The reason is unclear, and is maybe a bug in the gcc 4.8 - * linker that is used by LSB if facing a weak symbol (as "tzname"). - * This makes Orthanc crash if the timezone is set to UTC. - * https://groups.google.com/d/msg/orthanc-users/0m8sxxwSm1E/2p8du_89CAAJ - **/ - char *tzname[2] = { (char *) "GMT", (char *) "GMT" }; -} -# endif - -# endif -#endif - - - -#if defined(__unix__) && ORTHANC_SANDBOXED != 1 -# include "SystemToolbox.h" // Check out "InitializeGlobalLocale()" -#endif - - - -namespace Orthanc -{ - void Toolbox::LinesIterator::FindEndOfLine() - { - lineEnd_ = lineStart_; - - while (lineEnd_ < content_.size() && - content_[lineEnd_] != '\n' && - content_[lineEnd_] != '\r') - { - lineEnd_ += 1; - } - } - - - Toolbox::LinesIterator::LinesIterator(const std::string& content) : - content_(content), - lineStart_(0) - { - FindEndOfLine(); - } - - - bool Toolbox::LinesIterator::GetLine(std::string& target) const - { - assert(lineStart_ <= content_.size() && - lineEnd_ <= content_.size() && - lineStart_ <= lineEnd_); - - if (lineStart_ == content_.size()) - { - return false; - } - else - { - target = content_.substr(lineStart_, lineEnd_ - lineStart_); - return true; - } - } - - - void Toolbox::LinesIterator::Next() - { - lineStart_ = lineEnd_; - - if (lineStart_ != content_.size()) - { - assert(content_[lineStart_] == '\r' || - content_[lineStart_] == '\n'); - - char second; - - if (content_[lineStart_] == '\r') - { - second = '\n'; - } - else - { - second = '\r'; - } - - lineStart_ += 1; - - if (lineStart_ < content_.size() && - content_[lineStart_] == second) - { - lineStart_ += 1; - } - - FindEndOfLine(); - } - } - - - void Toolbox::ToUpperCase(std::string& s) - { - std::transform(s.begin(), s.end(), s.begin(), toupper); - } - - - void Toolbox::ToLowerCase(std::string& s) - { - std::transform(s.begin(), s.end(), s.begin(), tolower); - } - - - void Toolbox::ToUpperCase(std::string& result, - const std::string& source) - { - result = source; - ToUpperCase(result); - } - - void Toolbox::ToLowerCase(std::string& result, - const std::string& source) - { - result = source; - ToLowerCase(result); - } - - - void Toolbox::SplitUriComponents(UriComponents& components, - const std::string& uri) - { - static const char URI_SEPARATOR = '/'; - - components.clear(); - - if (uri.size() == 0 || - uri[0] != URI_SEPARATOR) - { - throw OrthancException(ErrorCode_UriSyntax); - } - - // Count the number of slashes in the URI to make an assumption - // about the number of components in the URI - unsigned int estimatedSize = 0; - for (unsigned int i = 0; i < uri.size(); i++) - { - if (uri[i] == URI_SEPARATOR) - estimatedSize++; - } - - components.reserve(estimatedSize - 1); - - unsigned int start = 1; - unsigned int end = 1; - while (end < uri.size()) - { - // This is the loop invariant - assert(uri[start - 1] == '/' && (end >= start)); - - if (uri[end] == '/') - { - components.push_back(std::string(&uri[start], end - start)); - end++; - start = end; - } - else - { - end++; - } - } - - if (start < uri.size()) - { - components.push_back(std::string(&uri[start], end - start)); - } - - for (size_t i = 0; i < components.size(); i++) - { - if (components[i].size() == 0) - { - // Empty component, as in: "/coucou//e" - throw OrthancException(ErrorCode_UriSyntax); - } - } - } - - - void Toolbox::TruncateUri(UriComponents& target, - const UriComponents& source, - size_t fromLevel) - { - target.clear(); - - if (source.size() > fromLevel) - { - target.resize(source.size() - fromLevel); - - size_t j = 0; - for (size_t i = fromLevel; i < source.size(); i++, j++) - { - target[j] = source[i]; - } - - assert(j == target.size()); - } - } - - - - bool Toolbox::IsChildUri(const UriComponents& baseUri, - const UriComponents& testedUri) - { - if (testedUri.size() < baseUri.size()) - { - return false; - } - - for (size_t i = 0; i < baseUri.size(); i++) - { - if (baseUri[i] != testedUri[i]) - return false; - } - - return true; - } - - - std::string Toolbox::FlattenUri(const UriComponents& components, - size_t fromLevel) - { - if (components.size() <= fromLevel) - { - return "/"; - } - else - { - std::string r; - - for (size_t i = fromLevel; i < components.size(); i++) - { - r += "/" + components[i]; - } - - return r; - } - } - - -#if ORTHANC_ENABLE_MD5 == 1 - static char GetHexadecimalCharacter(uint8_t value) - { - assert(value < 16); - - if (value < 10) - { - return value + '0'; - } - else - { - return (value - 10) + 'a'; - } - } - - - void Toolbox::ComputeMD5(std::string& result, - const std::string& data) - { - if (data.size() > 0) - { - ComputeMD5(result, &data[0], data.size()); - } - else - { - ComputeMD5(result, NULL, 0); - } - } - - - void Toolbox::ComputeMD5(std::string& result, - const void* data, - size_t size) - { - md5_state_s state; - md5_init(&state); - - if (size > 0) - { - md5_append(&state, - reinterpret_cast(data), - static_cast(size)); - } - - md5_byte_t actualHash[16]; - md5_finish(&state, actualHash); - - result.resize(32); - for (unsigned int i = 0; i < 16; i++) - { - result[2 * i] = GetHexadecimalCharacter(static_cast(actualHash[i] / 16)); - result[2 * i + 1] = GetHexadecimalCharacter(static_cast(actualHash[i] % 16)); - } - } -#endif - - -#if ORTHANC_ENABLE_BASE64 == 1 - void Toolbox::EncodeBase64(std::string& result, - const std::string& data) - { - result.clear(); - base64_encode(result, data); - } - - void Toolbox::DecodeBase64(std::string& result, - const std::string& data) - { - for (size_t i = 0; i < data.length(); i++) - { - if (!isalnum(data[i]) && - data[i] != '+' && - data[i] != '/' && - data[i] != '=') - { - // This is not a valid character for a Base64 string - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - result.clear(); - base64_decode(result, data); - } - - - bool Toolbox::DecodeDataUriScheme(std::string& mime, - std::string& content, - const std::string& source) - { - boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)", - boost::regex::icase /* case insensitive search */); - - boost::cmatch what; - if (regex_match(source.c_str(), what, pattern)) - { - mime = what[1]; - DecodeBase64(content, what[2]); - return true; - } - else - { - return false; - } - } - - - void Toolbox::EncodeDataUriScheme(std::string& result, - const std::string& mime, - const std::string& content) - { - result = "data:" + mime + ";base64,"; - base64_encode(result, content); - } - -#endif - - -#if ORTHANC_ENABLE_LOCALE == 1 - static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding) - { - switch (sourceEncoding) - { - case Encoding_Utf8: - return "UTF-8"; - - case Encoding_Ascii: - return "ASCII"; - - case Encoding_Latin1: - return "ISO-8859-1"; - - case Encoding_Latin2: - return "ISO-8859-2"; - - case Encoding_Latin3: - return "ISO-8859-3"; - - case Encoding_Latin4: - return "ISO-8859-4"; - - case Encoding_Latin5: - return "ISO-8859-9"; - - case Encoding_Cyrillic: - return "ISO-8859-5"; - - case Encoding_Windows1251: - return "WINDOWS-1251"; - - case Encoding_Arabic: - return "ISO-8859-6"; - - case Encoding_Greek: - return "ISO-8859-7"; - - case Encoding_Hebrew: - return "ISO-8859-8"; - - case Encoding_Japanese: - return "SHIFT-JIS"; - - case Encoding_Chinese: - return "GB18030"; - - case Encoding_Thai: -#if BOOST_LOCALE_WITH_ICU == 1 - return "tis620.2533"; -#else - return "TIS620.2533-0"; -#endif - - case Encoding_Korean: - return "ISO-IR-149"; - - case Encoding_JapaneseKanji: - return "JIS"; - - case Encoding_SimplifiedChinese: - return "GB2312"; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } -#endif - - -#if ORTHANC_ENABLE_LOCALE == 1 - // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2 - std::string Toolbox::ConvertToUtf8(const std::string& source, - Encoding sourceEncoding, - bool hasCodeExtensions) - { -#if ORTHANC_STATIC_ICU == 1 - if (globalIcuData_.empty()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "Call Toolbox::InitializeGlobalLocale()"); - } -#endif - - // The "::skip" flag makes boost skip invalid UTF-8 - // characters. This can occur in badly-encoded DICOM files. - - try - { - if (sourceEncoding == Encoding_Ascii) - { - return ConvertToAscii(source); - } - else - { - std::string s; - - if (sourceEncoding == Encoding_Utf8) - { - // Already in UTF-8: No conversion is required, but we ensure - // the output is correctly encoded - s = boost::locale::conv::utf_to_utf(source, boost::locale::conv::skip); - } - else - { - const char* encoding = GetBoostLocaleEncoding(sourceEncoding); - s = boost::locale::conv::to_utf(source, encoding, boost::locale::conv::skip); - } - - if (hasCodeExtensions) - { - std::string t; - RemoveIso2022EscapeSequences(t, s); - return t; - } - else - { - return s; - } - } - } - catch (std::runtime_error& e) - { - // Bad input string or bad encoding - LOG(INFO) << e.what(); - return ConvertToAscii(source); - } - } -#endif - - -#if ORTHANC_ENABLE_LOCALE == 1 - std::string Toolbox::ConvertFromUtf8(const std::string& source, - Encoding targetEncoding) - { -#if ORTHANC_STATIC_ICU == 1 - if (globalIcuData_.empty()) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "Call Toolbox::InitializeGlobalLocale()"); - } -#endif - - // The "::skip" flag makes boost skip invalid UTF-8 - // characters. This can occur in badly-encoded DICOM files. - - try - { - if (targetEncoding == Encoding_Utf8) - { - // Already in UTF-8: No conversion is required. - return boost::locale::conv::utf_to_utf(source, boost::locale::conv::skip); - } - else if (targetEncoding == Encoding_Ascii) - { - return ConvertToAscii(source); - } - else - { - const char* encoding = GetBoostLocaleEncoding(targetEncoding); - return boost::locale::conv::from_utf(source, encoding, boost::locale::conv::skip); - } - } - catch (std::runtime_error&) - { - // Bad input string or bad encoding - return ConvertToAscii(source); - } - } -#endif - - - static bool IsAsciiCharacter(uint8_t c) - { - return (c != 0 && - c <= 127 && - (c == '\n' || !iscntrl(c))); - } - - - bool Toolbox::IsAsciiString(const void* data, - size_t size) - { - const uint8_t* p = reinterpret_cast(data); - - for (size_t i = 0; i < size; i++, p++) - { - if (!IsAsciiCharacter(*p)) - { - return false; - } - } - - return true; - } - - - bool Toolbox::IsAsciiString(const std::string& s) - { - return IsAsciiString(s.c_str(), s.size()); - } - - - std::string Toolbox::ConvertToAscii(const std::string& source) - { - std::string result; - - result.reserve(source.size() + 1); - for (size_t i = 0; i < source.size(); i++) - { - if (IsAsciiCharacter(source[i])) - { - result.push_back(source[i]); - } - } - - return result; - } - - - void Toolbox::ComputeSHA1(std::string& result, - const void* data, - size_t size) - { - boost::uuids::detail::sha1 sha1; - - if (size > 0) - { - sha1.process_bytes(data, size); - } - - unsigned int digest[5]; - - // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide - assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); - - sha1.get_digest(digest); - - result.resize(8 * 5 + 4); - sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x", - digest[0], - digest[1], - digest[2], - digest[3], - digest[4]); - } - - void Toolbox::ComputeSHA1(std::string& result, - const std::string& data) - { - if (data.size() > 0) - { - ComputeSHA1(result, data.c_str(), data.size()); - } - else - { - ComputeSHA1(result, NULL, 0); - } - } - - - bool Toolbox::IsSHA1(const void* str, - size_t size) - { - if (size == 0) - { - return false; - } - - const char* start = reinterpret_cast(str); - const char* end = start + size; - - // Trim the beginning of the string - while (start < end) - { - if (*start == '\0' || - isspace(*start)) - { - start++; - } - else - { - break; - } - } - - // Trim the trailing of the string - while (start < end) - { - if (*(end - 1) == '\0' || - isspace(*(end - 1))) - { - end--; - } - else - { - break; - } - } - - if (end - start != 44) - { - return false; - } - - for (unsigned int i = 0; i < 44; i++) - { - if (i == 8 || - i == 17 || - i == 26 || - i == 35) - { - if (start[i] != '-') - return false; - } - else - { - if (!isalnum(start[i])) - return false; - } - } - - return true; - } - - - bool Toolbox::IsSHA1(const std::string& s) - { - if (s.size() == 0) - { - return false; - } - else - { - return IsSHA1(s.c_str(), s.size()); - } - } - - - std::string Toolbox::StripSpaces(const std::string& source) - { - size_t first = 0; - - while (first < source.length() && - isspace(source[first])) - { - first++; - } - - if (first == source.length()) - { - // String containing only spaces - return ""; - } - - size_t last = source.length(); - while (last > first && - isspace(source[last - 1])) - { - last--; - } - - assert(first <= last); - return source.substr(first, last - first); - } - - - static char Hex2Dec(char c) - { - return ((c >= '0' && c <= '9') ? c - '0' : - ((c >= 'a' && c <= 'f') ? c - 'a' + 10 : c - 'A' + 10)); - } - - void Toolbox::UrlDecode(std::string& s) - { - // http://en.wikipedia.org/wiki/Percent-encoding - // http://www.w3schools.com/tags/ref_urlencode.asp - // http://stackoverflow.com/questions/154536/encode-decode-urls-in-c - - if (s.size() == 0) - { - return; - } - - size_t source = 0; - size_t target = 0; - - while (source < s.size()) - { - if (s[source] == '%' && - source + 2 < s.size() && - isalnum(s[source + 1]) && - isalnum(s[source + 2])) - { - s[target] = (Hex2Dec(s[source + 1]) << 4) | Hex2Dec(s[source + 2]); - source += 3; - target += 1; - } - else - { - if (s[source] == '+') - s[target] = ' '; - else - s[target] = s[source]; - - source++; - target++; - } - } - - s.resize(target); - } - - - Endianness Toolbox::DetectEndianness() - { - // http://sourceforge.net/p/predef/wiki/Endianness/ - - uint32_t bufferView; - - uint8_t* buffer = reinterpret_cast(&bufferView); - - buffer[0] = 0x00; - buffer[1] = 0x01; - buffer[2] = 0x02; - buffer[3] = 0x03; - - switch (bufferView) - { - case 0x00010203: - return Endianness_Big; - - case 0x03020100: - return Endianness_Little; - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - std::string Toolbox::WildcardToRegularExpression(const std::string& source) - { - // TODO - Speed up this with a regular expression - - std::string result = source; - - // Escape all special characters - boost::replace_all(result, "\\", "\\\\"); - boost::replace_all(result, "^", "\\^"); - boost::replace_all(result, ".", "\\."); - boost::replace_all(result, "$", "\\$"); - boost::replace_all(result, "|", "\\|"); - boost::replace_all(result, "(", "\\("); - boost::replace_all(result, ")", "\\)"); - boost::replace_all(result, "[", "\\["); - boost::replace_all(result, "]", "\\]"); - boost::replace_all(result, "+", "\\+"); - boost::replace_all(result, "/", "\\/"); - boost::replace_all(result, "{", "\\{"); - boost::replace_all(result, "}", "\\}"); - - // Convert wildcards '*' and '?' to their regex equivalents - boost::replace_all(result, "?", "."); - boost::replace_all(result, "*", ".*"); - - return result; - } - - - void Toolbox::TokenizeString(std::vector& result, - const std::string& value, - char separator) - { - size_t countSeparators = 0; - - for (size_t i = 0; i < value.size(); i++) - { - if (value[i] == separator) - { - countSeparators++; - } - } - - result.clear(); - result.reserve(countSeparators + 1); - - std::string currentItem; - - for (size_t i = 0; i < value.size(); i++) - { - if (value[i] == separator) - { - result.push_back(currentItem); - currentItem.clear(); - } - else - { - currentItem.push_back(value[i]); - } - } - - result.push_back(currentItem); - } - - -#if ORTHANC_ENABLE_PUGIXML == 1 - class ChunkedBufferWriter : public pugi::xml_writer - { - private: - ChunkedBuffer buffer_; - - public: - virtual void write(const void *data, size_t size) - { - if (size > 0) - { - buffer_.AddChunk(reinterpret_cast(data), size); - } - } - - void Flatten(std::string& s) - { - buffer_.Flatten(s); - } - }; - - - static void JsonToXmlInternal(pugi::xml_node& target, - const Json::Value& source, - const std::string& arrayElement) - { - // http://jsoncpp.sourceforge.net/value_8h_source.html#l00030 - - switch (source.type()) - { - case Json::nullValue: - { - target.append_child(pugi::node_pcdata).set_value("null"); - break; - } - - case Json::intValue: - { - std::string s = boost::lexical_cast(source.asInt()); - target.append_child(pugi::node_pcdata).set_value(s.c_str()); - break; - } - - case Json::uintValue: - { - std::string s = boost::lexical_cast(source.asUInt()); - target.append_child(pugi::node_pcdata).set_value(s.c_str()); - break; - } - - case Json::realValue: - { - std::string s = boost::lexical_cast(source.asFloat()); - target.append_child(pugi::node_pcdata).set_value(s.c_str()); - break; - } - - case Json::stringValue: - { - target.append_child(pugi::node_pcdata).set_value(source.asString().c_str()); - break; - } - - case Json::booleanValue: - { - target.append_child(pugi::node_pcdata).set_value(source.asBool() ? "true" : "false"); - break; - } - - case Json::arrayValue: - { - for (Json::Value::ArrayIndex i = 0; i < source.size(); i++) - { - pugi::xml_node node = target.append_child(); - node.set_name(arrayElement.c_str()); - JsonToXmlInternal(node, source[i], arrayElement); - } - break; - } - - case Json::objectValue: - { - Json::Value::Members members = source.getMemberNames(); - - for (size_t i = 0; i < members.size(); i++) - { - pugi::xml_node node = target.append_child(); - node.set_name(members[i].c_str()); - JsonToXmlInternal(node, source[members[i]], arrayElement); - } - - break; - } - - default: - throw OrthancException(ErrorCode_NotImplemented); - } - } - - - void Toolbox::JsonToXml(std::string& target, - const Json::Value& source, - const std::string& rootElement, - const std::string& arrayElement) - { - pugi::xml_document doc; - - pugi::xml_node n = doc.append_child(rootElement.c_str()); - JsonToXmlInternal(n, source, arrayElement); - - pugi::xml_node decl = doc.prepend_child(pugi::node_declaration); - decl.append_attribute("version").set_value("1.0"); - decl.append_attribute("encoding").set_value("utf-8"); - - XmlToString(target, doc); - } - - void Toolbox::XmlToString(std::string& target, - const pugi::xml_document& source) - { - ChunkedBufferWriter writer; - source.save(writer, " ", pugi::format_default, pugi::encoding_utf8); - writer.Flatten(target); - } -#endif - - - - bool Toolbox::IsInteger(const std::string& str) - { - std::string s = StripSpaces(str); - - if (s.size() == 0) - { - return false; - } - - size_t pos = 0; - if (s[0] == '-') - { - if (s.size() == 1) - { - return false; - } - - pos = 1; - } - - while (pos < s.size()) - { - if (!isdigit(s[pos])) - { - return false; - } - - pos++; - } - - return true; - } - - - void Toolbox::CopyJsonWithoutComments(Json::Value& target, - const Json::Value& source) - { - switch (source.type()) - { - case Json::nullValue: - target = Json::nullValue; - break; - - case Json::intValue: - target = source.asInt64(); - break; - - case Json::uintValue: - target = source.asUInt64(); - break; - - case Json::realValue: - target = source.asDouble(); - break; - - case Json::stringValue: - target = source.asString(); - break; - - case Json::booleanValue: - target = source.asBool(); - break; - - case Json::arrayValue: - { - target = Json::arrayValue; - for (Json::Value::ArrayIndex i = 0; i < source.size(); i++) - { - Json::Value& item = target.append(Json::nullValue); - CopyJsonWithoutComments(item, source[i]); - } - - break; - } - - case Json::objectValue: - { - target = Json::objectValue; - Json::Value::Members members = source.getMemberNames(); - for (Json::Value::ArrayIndex i = 0; i < members.size(); i++) - { - const std::string item = members[i]; - CopyJsonWithoutComments(target[item], source[item]); - } - - break; - } - - default: - break; - } - } - - - bool Toolbox::StartsWith(const std::string& str, - const std::string& prefix) - { - if (str.size() < prefix.size()) - { - return false; - } - else - { - return str.compare(0, prefix.size(), prefix) == 0; - } - } - - - static bool IsUnreservedCharacter(char c) - { - // This function checks whether "c" is an unserved character - // wrt. an URI percent-encoding - // https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding%5Fin%5Fa%5FURI - - return ((c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9') || - c == '-' || - c == '_' || - c == '.' || - c == '~'); - } - - void Toolbox::UriEncode(std::string& target, - const std::string& source) - { - // Estimate the length of the percent-encoded URI - size_t length = 0; - - for (size_t i = 0; i < source.size(); i++) - { - if (IsUnreservedCharacter(source[i])) - { - length += 1; - } - else - { - // This character must be percent-encoded - length += 3; - } - } - - target.clear(); - target.reserve(length); - - for (size_t i = 0; i < source.size(); i++) - { - if (IsUnreservedCharacter(source[i])) - { - target.push_back(source[i]); - } - else - { - // This character must be percent-encoded - uint8_t byte = static_cast(source[i]); - uint8_t a = byte >> 4; - uint8_t b = byte & 0x0f; - - target.push_back('%'); - target.push_back(a < 10 ? a + '0' : a - 10 + 'A'); - target.push_back(b < 10 ? b + '0' : b - 10 + 'A'); - } - } - } - - - static bool HasField(const Json::Value& json, - const std::string& key, - Json::ValueType expectedType) - { - if (json.type() != Json::objectValue || - !json.isMember(key)) - { - return false; - } - else if (json[key].type() == expectedType) - { - return true; - } - else - { - throw OrthancException(ErrorCode_BadParameterType); - } - } - - - std::string Toolbox::GetJsonStringField(const Json::Value& json, - const std::string& key, - const std::string& defaultValue) - { - if (HasField(json, key, Json::stringValue)) - { - return json[key].asString(); - } - else - { - return defaultValue; - } - } - - - bool Toolbox::GetJsonBooleanField(const ::Json::Value& json, - const std::string& key, - bool defaultValue) - { - if (HasField(json, key, Json::booleanValue)) - { - return json[key].asBool(); - } - else - { - return defaultValue; - } - } - - - int Toolbox::GetJsonIntegerField(const ::Json::Value& json, - const std::string& key, - int defaultValue) - { - if (HasField(json, key, Json::intValue)) - { - return json[key].asInt(); - } - else - { - return defaultValue; - } - } - - - unsigned int Toolbox::GetJsonUnsignedIntegerField(const ::Json::Value& json, - const std::string& key, - unsigned int defaultValue) - { - int v = GetJsonIntegerField(json, key, defaultValue); - - if (v < 0) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - else - { - return static_cast(v); - } - } - - - bool Toolbox::IsUuid(const std::string& str) - { - if (str.size() != 36) - { - return false; - } - - for (size_t i = 0; i < str.length(); i++) - { - if (i == 8 || i == 13 || i == 18 || i == 23) - { - if (str[i] != '-') - return false; - } - else - { - if (!isalnum(str[i])) - return false; - } - } - - return true; - } - - - bool Toolbox::StartsWithUuid(const std::string& str) - { - if (str.size() < 36) - { - return false; - } - - if (str.size() == 36) - { - return IsUuid(str); - } - - assert(str.size() > 36); - if (!isspace(str[36])) - { - return false; - } - - return IsUuid(str.substr(0, 36)); - } - - -#if ORTHANC_ENABLE_LOCALE == 1 - static std::unique_ptr globalLocale_; - - static bool SetGlobalLocale(const char* locale) - { - try - { - if (locale == NULL) - { - LOG(WARNING) << "Falling back to system-wide default locale"; - globalLocale_.reset(new std::locale()); - } - else - { - LOG(INFO) << "Using locale: \"" << locale << "\" for case-insensitive comparison of strings"; - globalLocale_.reset(new std::locale(locale)); - } - } - catch (std::runtime_error& e) - { - LOG(ERROR) << "Cannot set globale locale to " - << (locale ? std::string(locale) : "(null)") - << ": " << e.what(); - globalLocale_.reset(NULL); - } - - return (globalLocale_.get() != NULL); - } - - - static void InitializeIcu() - { -#if ORTHANC_STATIC_ICU == 1 - if (globalIcuData_.empty()) - { - LOG(INFO) << "Setting up the ICU common data"; - - GzipCompressor compressor; - compressor.Uncompress(globalIcuData_, - FrameworkResources::GetFileResourceBuffer(FrameworkResources::LIBICU_DATA), - FrameworkResources::GetFileResourceSize(FrameworkResources::LIBICU_DATA)); - - std::string md5; - Toolbox::ComputeMD5(md5, globalIcuData_); - - if (md5 != ORTHANC_ICU_DATA_MD5 || - globalIcuData_.empty()) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot decode the ICU common data"); - } - - // "ICU data is designed to be 16-aligned" - // http://userguide.icu-project.org/icudata#TOC-Alignment - - { - static const size_t ALIGN = 16; - - UErrorCode status = U_ZERO_ERROR; - - if (reinterpret_cast(globalIcuData_.c_str()) % ALIGN == 0) - { - // Data is already properly aligned - udata_setCommonData(globalIcuData_.c_str(), &status); - } - else - { - std::string aligned; - aligned.resize(globalIcuData_.size() + ALIGN - 1); - - intptr_t offset = reinterpret_cast(aligned.c_str()) % ALIGN; - if (offset != 0) - { - offset = ALIGN - offset; - } - - if (offset + globalIcuData_.size() > aligned.size()) - { - throw OrthancException(ErrorCode_InternalError, "Cannot align on 16-bytes boundary"); - } - - // We don't use "memcpy()", as it expects its data to be aligned - const uint8_t* p = reinterpret_cast(&globalIcuData_[0]); - uint8_t* q = reinterpret_cast(&aligned[0]) + offset; - for (size_t i = 0; i < globalIcuData_.size(); i++, p++, q++) - { - *q = *p; - } - - globalIcuData_.swap(aligned); - - const uint8_t* data = reinterpret_cast(globalIcuData_.c_str()) + offset; - - if (reinterpret_cast(data) % ALIGN != 0) - { - throw OrthancException(ErrorCode_InternalError, "Cannot align on 16-bytes boundary"); - } - else - { - udata_setCommonData(data, &status); - } - } - - if (status != U_ZERO_ERROR) - { - throw OrthancException(ErrorCode_InternalError, "Cannot initialize ICU"); - } - } - - if (Toolbox::DetectEndianness() != Endianness_Little) - { - // TODO - The data table must be swapped (uint16_t) - throw OrthancException(ErrorCode_NotImplemented); - } - - // "First-use of ICU from a single thread before the - // multi-threaded use of ICU begins", to make sure everything is - // properly initialized (should not be mandatory in our - // case). We let boost handle calls to "u_init()" and "u_cleanup()". - // http://userguide.icu-project.org/design#TOC-ICU-Initialization-and-Termination - uloc_getDefault(); - } -#endif - } - - void Toolbox::InitializeGlobalLocale(const char* locale) - { - InitializeIcu(); - -#if defined(__unix__) && ORTHANC_SANDBOXED != 1 - static const char* LOCALTIME = "/etc/localtime"; - - if (!SystemToolbox::IsExistingFile(LOCALTIME)) - { - // Check out file - // "boost_1_69_0/libs/locale/src/icu/time_zone.cpp": Direct - // access is made to this file if ICU is not used. Crash arises - // in Boost if the file is a symbolic link to a non-existing - // file (such as in Ubuntu 16.04 base Docker image). - throw OrthancException( - ErrorCode_InternalError, - "On UNIX-like systems, the file " + std::string(LOCALTIME) + - " must be present on the filesystem (install \"tzdata\" package on Debian)"); - } -#endif - - // Make Orthanc use English, United States locale - // Linux: use "en_US.UTF-8" - // Windows: use "" - // Wine: use NULL - -#if defined(__MINGW32__) - // Visibly, there is no support of locales in MinGW yet - // http://mingw.5.n7.nabble.com/How-to-use-std-locale-global-with-MinGW-correct-td33048.html - static const char* DEFAULT_LOCALE = NULL; -#elif defined(_WIN32) - // For Windows: use default locale (using "en_US" does not work) - static const char* DEFAULT_LOCALE = ""; -#else - // For Linux & cie - static const char* DEFAULT_LOCALE = "en_US.UTF-8"; -#endif - - bool ok; - - if (locale == NULL) - { - ok = SetGlobalLocale(DEFAULT_LOCALE); - -#if defined(__MINGW32__) - LOG(WARNING) << "This is a MinGW build, case-insensitive comparison of " - << "strings with accents will not work outside of Wine"; -#endif - } - else - { - ok = SetGlobalLocale(locale); - } - - if (!ok && - !SetGlobalLocale(NULL)) - { - throw OrthancException(ErrorCode_InternalError, - "Cannot initialize global locale"); - } - - } - - - void Toolbox::FinalizeGlobalLocale() - { - globalLocale_.reset(); - } - - - std::string Toolbox::ToUpperCaseWithAccents(const std::string& source) - { - bool error = (globalLocale_.get() == NULL); - -#if ORTHANC_STATIC_ICU == 1 - if (globalIcuData_.empty()) - { - error = true; - } -#endif - - if (error) - { - throw OrthancException(ErrorCode_BadSequenceOfCalls, - "No global locale was set, call Toolbox::InitializeGlobalLocale()"); - } - - /** - * A few notes about locales: - * - * (1) We don't use "case folding": - * http://www.boost.org/doc/libs/1_64_0/libs/locale/doc/html/conversions.html - * - * Characters are made uppercase one by one. This is because, in - * static builds, we are using iconv, which is visibly not - * supported correctly (TODO: Understand why). Case folding seems - * to be working correctly if using the default backend under - * Linux (ICU or POSIX?). If one wishes to use case folding, one - * would use: - * - * boost::locale::generator gen; - * std::locale::global(gen(DEFAULT_LOCALE)); - * return boost::locale::to_upper(source); - * - * (2) The function "boost::algorithm::to_upper_copy" does not - * make use of the "std::locale::global()". We therefore create a - * global variable "globalLocale_". - * - * (3) The variant of "boost::algorithm::to_upper_copy()" that - * uses std::string does not work properly. We need to apply it - * one wide strings (std::wstring). This explains the two calls to - * "utf_to_utf" in order to convert to/from std::wstring. - **/ - - std::wstring w = boost::locale::conv::utf_to_utf(source, boost::locale::conv::skip); - w = boost::algorithm::to_upper_copy(w, *globalLocale_); - return boost::locale::conv::utf_to_utf(w, boost::locale::conv::skip); - } -#endif - - - -#if ORTHANC_ENABLE_SSL == 0 - /** - * OpenSSL is disabled - **/ - void Toolbox::InitializeOpenSsl() - { - } - - void Toolbox::FinalizeOpenSsl() - { - } - - -#elif (ORTHANC_ENABLE_SSL == 1 && \ - OPENSSL_VERSION_NUMBER < 0x10100000L) - /** - * OpenSSL < 1.1.0 - **/ - void Toolbox::InitializeOpenSsl() - { - // https://wiki.openssl.org/index.php/Library_Initialization - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); - ERR_load_crypto_strings(); - } - - void Toolbox::FinalizeOpenSsl() - { - // Finalize OpenSSL - // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup -#ifdef FIPS_mode_set - FIPS_mode_set(0); -#endif - -#if !defined(OPENSSL_NO_ENGINE) - ENGINE_cleanup(); -#endif - - CONF_modules_unload(1); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); - ERR_remove_state(0); - ERR_free_strings(); - } - - -#elif (ORTHANC_ENABLE_SSL == 1 && \ - OPENSSL_VERSION_NUMBER >= 0x10100000L) - /** - * OpenSSL >= 1.1.0. In this case, the initialization is - * automatically done by the functions of OpenSSL. - * https://wiki.openssl.org/index.php/Library_Initialization - **/ - void Toolbox::InitializeOpenSsl() - { - } - - void Toolbox::FinalizeOpenSsl() - { - } - -#else -# error "Support your platform here" -#endif - - - - std::string Toolbox::GenerateUuid() - { -#ifdef WIN32 - UUID uuid; - UuidCreate ( &uuid ); - - unsigned char * str; - UuidToStringA ( &uuid, &str ); - - std::string s( ( char* ) str ); - - RpcStringFreeA ( &str ); -#else - uuid_t uuid; - uuid_generate_random ( uuid ); - char s[37]; - uuid_unparse ( uuid, s ); -#endif - return s; - } - - - namespace - { - // Anonymous namespace to avoid clashes between compilation modules - - class VariableFormatter - { - public: - typedef std::map Dictionary; - - private: - const Dictionary& dictionary_; - - public: - VariableFormatter(const Dictionary& dictionary) : - dictionary_(dictionary) - { - } - - template - Out operator()(const boost::smatch& what, - Out out) const - { - if (!what[1].str().empty()) - { - // Variable without a default value - Dictionary::const_iterator found = dictionary_.find(what[1]); - - if (found != dictionary_.end()) - { - const std::string& value = found->second; - out = std::copy(value.begin(), value.end(), out); - } - } - else - { - // Variable with a default value - std::string key; - std::string defaultValue; - - if (!what[2].str().empty()) - { - key = what[2].str(); - defaultValue = what[3].str(); - } - else if (!what[4].str().empty()) - { - key = what[4].str(); - defaultValue = what[5].str(); - } - else if (!what[6].str().empty()) - { - key = what[6].str(); - defaultValue = what[7].str(); - } - else - { - throw OrthancException(ErrorCode_InternalError); - } - - Dictionary::const_iterator found = dictionary_.find(key); - - if (found == dictionary_.end()) - { - out = std::copy(defaultValue.begin(), defaultValue.end(), out); - } - else - { - const std::string& value = found->second; - out = std::copy(value.begin(), value.end(), out); - } - } - - return out; - } - }; - } - - - std::string Toolbox::SubstituteVariables(const std::string& source, - const std::map& dictionary) - { - const boost::regex pattern("\\$\\{([^:]*?)\\}|" // ${what[1]} - "\\$\\{([^:]*?):-([^'\"]*?)\\}|" // ${what[2]:-what[3]} - "\\$\\{([^:]*?):-\"([^\"]*?)\"\\}|" // ${what[4]:-"what[5]"} - "\\$\\{([^:]*?):-'([^']*?)'\\}"); // ${what[6]:-'what[7]'} - - VariableFormatter formatter(dictionary); - - return boost::regex_replace(source, pattern, formatter); - } - - - namespace Iso2022 - { - /** - Returns whether the string s contains a single-byte control message - at index i - **/ - static inline bool IsControlMessage1(const std::string& s, size_t i) - { - if (i < s.size()) - { - char c = s[i]; - return - (c == '\x0f') || // Locking shift zero - (c == '\x0e'); // Locking shift one - } - else - { - return false; - } - } - - /** - Returns whether the string s contains a double-byte control message - at index i - **/ - static inline size_t IsControlMessage2(const std::string& s, size_t i) - { - if (i + 1 < s.size()) - { - char c1 = s[i]; - char c2 = s[i + 1]; - return (c1 == 0x1b) && ( - (c2 == '\x6e') || // Locking shift two - (c2 == '\x6f') || // Locking shift three - (c2 == '\x4e') || // Single shift two (alt) - (c2 == '\x4f') || // Single shift three (alt) - (c2 == '\x7c') || // Locking shift three right - (c2 == '\x7d') || // Locking shift two right - (c2 == '\x7e') // Locking shift one right - ); - } - else - { - return false; - } - } - - /** - Returns whether the string s contains a triple-byte control message - at index i - **/ - static inline size_t IsControlMessage3(const std::string& s, size_t i) - { - if (i + 2 < s.size()) - { - char c1 = s[i]; - char c2 = s[i + 1]; - char c3 = s[i + 2]; - return ((c1 == '\x8e' && c2 == 0x1b && c3 == '\x4e') || - (c1 == '\x8f' && c2 == 0x1b && c3 == '\x4f')); - } - else - { - return false; - } - } - - /** - This function returns true if the index i in the supplied string s: - - is valid - - contains the c character - This function returns false otherwise. - **/ - static inline bool TestCharValue( - const std::string& s, size_t i, char c) - { - if (i < s.size()) - return s[i] == c; - else - return false; - } - - /** - This function returns true if the index i in the supplied string s: - - is valid - - has a c character that is >= cMin and <= cMax (included) - This function returns false otherwise. - **/ - static inline bool TestCharRange( - const std::string& s, size_t i, char cMin, char cMax) - { - if (i < s.size()) - return (s[i] >= cMin) && (s[i] <= cMax); - else - return false; - } - - /** - This function returns the total length in bytes of the escape sequence - located in string s at index i, if there is one, or 0 otherwise. - **/ - static inline size_t GetEscapeSequenceLength(const std::string& s, size_t i) - { - if (TestCharValue(s, i, 0x1b)) - { - size_t j = i+1; - - // advance reading cursor while we are in a sequence - while (TestCharRange(s, j, '\x20', '\x2f')) - ++j; - - // check there is a valid termination byte AND we're long enough (there - // must be at least one byte between 0x20 and 0x2f - if (TestCharRange(s, j, '\x30', '\x7f') && (j - i) >= 2) - return j - i + 1; - else - return 0; - } - else - return 0; - } - } - - - - /** - This function will strip all ISO/IEC 2022 control codes and escape - sequences. - Please see https://en.wikipedia.org/wiki/ISO/IEC_2022 (as of 2019-02) - for a list of those. - - Please note that this operation is potentially destructive, because - it removes the character set information from the byte stream. - - However, in the case where the encoding is unique, then suppressing - the escape sequences allows one to provide us with a clean string after - conversion to utf-8 with boost. - **/ - void Toolbox::RemoveIso2022EscapeSequences(std::string& dest, const std::string& src) - { - // we need AT MOST the same size as the source string in the output - dest.clear(); - if (dest.capacity() < src.size()) - dest.reserve(src.size()); - - size_t i = 0; - - // uint8_t view to the string - while (i < src.size()) - { - size_t j = i; - - // The i index will only be incremented if a message is detected - // in that case, the message is skipped and the index is set to the - // next position to read - if (Iso2022::IsControlMessage1(src, i)) - i += 1; - else if (Iso2022::IsControlMessage2(src, i)) - i += 2; - else if (Iso2022::IsControlMessage3(src, i)) - i += 3; - else - i += Iso2022::GetEscapeSequenceLength(src, i); - - // if the index was NOT incremented, this means there was no message at - // this location: we then may copy the character at this index and - // increment the index to point to the next read position - if (j == i) - { - dest.push_back(src[i]); - i++; - } - } - } - - - void Toolbox::Utf8ToUnicodeCharacter(uint32_t& unicode, - size_t& length, - const std::string& utf8, - size_t position) - { - // https://en.wikipedia.org/wiki/UTF-8 - - static const uint8_t MASK_IS_1_BYTE = 0x80; // printf '0x%x\n' "$((2#10000000))" - static const uint8_t TEST_IS_1_BYTE = 0x00; - - static const uint8_t MASK_IS_2_BYTES = 0xe0; // printf '0x%x\n' "$((2#11100000))" - static const uint8_t TEST_IS_2_BYTES = 0xc0; // printf '0x%x\n' "$((2#11000000))" - - static const uint8_t MASK_IS_3_BYTES = 0xf0; // printf '0x%x\n' "$((2#11110000))" - static const uint8_t TEST_IS_3_BYTES = 0xe0; // printf '0x%x\n' "$((2#11100000))" - - static const uint8_t MASK_IS_4_BYTES = 0xf8; // printf '0x%x\n' "$((2#11111000))" - static const uint8_t TEST_IS_4_BYTES = 0xf0; // printf '0x%x\n' "$((2#11110000))" - - static const uint8_t MASK_CONTINUATION = 0xc0; // printf '0x%x\n' "$((2#11000000))" - static const uint8_t TEST_CONTINUATION = 0x80; // printf '0x%x\n' "$((2#10000000))" - - if (position >= utf8.size()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - assert(sizeof(uint8_t) == sizeof(char)); - const uint8_t* buffer = reinterpret_cast(utf8.c_str()) + position; - - if ((buffer[0] & MASK_IS_1_BYTE) == TEST_IS_1_BYTE) - { - length = 1; - unicode = buffer[0] & ~MASK_IS_1_BYTE; - } - else if ((buffer[0] & MASK_IS_2_BYTES) == TEST_IS_2_BYTES && - position + 1 < utf8.size() && - (buffer[1] & MASK_CONTINUATION) == TEST_CONTINUATION) - { - length = 2; - uint32_t a = buffer[0] & ~MASK_IS_2_BYTES; - uint32_t b = buffer[1] & ~MASK_CONTINUATION; - unicode = (a << 6) | b; - } - else if ((buffer[0] & MASK_IS_3_BYTES) == TEST_IS_3_BYTES && - position + 2 < utf8.size() && - (buffer[1] & MASK_CONTINUATION) == TEST_CONTINUATION && - (buffer[2] & MASK_CONTINUATION) == TEST_CONTINUATION) - { - length = 3; - uint32_t a = buffer[0] & ~MASK_IS_3_BYTES; - uint32_t b = buffer[1] & ~MASK_CONTINUATION; - uint32_t c = buffer[2] & ~MASK_CONTINUATION; - unicode = (a << 12) | (b << 6) | c; - } - else if ((buffer[0] & MASK_IS_4_BYTES) == TEST_IS_4_BYTES && - position + 3 < utf8.size() && - (buffer[1] & MASK_CONTINUATION) == TEST_CONTINUATION && - (buffer[2] & MASK_CONTINUATION) == TEST_CONTINUATION && - (buffer[3] & MASK_CONTINUATION) == TEST_CONTINUATION) - { - length = 4; - uint32_t a = buffer[0] & ~MASK_IS_4_BYTES; - uint32_t b = buffer[1] & ~MASK_CONTINUATION; - uint32_t c = buffer[2] & ~MASK_CONTINUATION; - uint32_t d = buffer[3] & ~MASK_CONTINUATION; - unicode = (a << 18) | (b << 12) | (c << 6) | d; - } - else - { - // This is not a valid UTF-8 encoding - throw OrthancException(ErrorCode_BadFileFormat, "Invalid UTF-8 string"); - } - } - - - std::string Toolbox::LargeHexadecimalToDecimal(const std::string& hex) - { - /** - * NB: Focus of the code below is *not* efficiency, but - * readability! - **/ - - for (size_t i = 0; i < hex.size(); i++) - { - const char c = hex[i]; - if (!((c >= 'A' && c <= 'F') || - (c >= 'a' && c <= 'f') || - (c >= '0' && c <= '9'))) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "Not an hexadecimal number"); - } - } - - std::vector decimal; - decimal.push_back(0); - - for (size_t i = 0; i < hex.size(); i++) - { - uint8_t hexDigit = static_cast(Hex2Dec(hex[i])); - assert(hexDigit <= 15); - - for (size_t j = 0; j < decimal.size(); j++) - { - uint8_t val = static_cast(decimal[j]) * 16 + hexDigit; // Maximum: 9 * 16 + 15 - assert(val <= 159 /* == 9 * 16 + 15 */); - - decimal[j] = val % 10; - hexDigit = val / 10; - assert(hexDigit <= 15 /* == 159 / 10 */); - } - - while (hexDigit > 0) - { - decimal.push_back(hexDigit % 10); - hexDigit /= 10; - } - } - - size_t start = 0; - while (start < decimal.size() && - decimal[start] == '0') - { - start++; - } - - std::string s; - s.reserve(decimal.size() - start); - - for (size_t i = decimal.size(); i > start; i--) - { - s.push_back(decimal[i - 1] + '0'); - } - - return s; - } - - - std::string Toolbox::GenerateDicomPrivateUniqueIdentifier() - { - /** - * REFERENCE: "Creating a Privately Defined Unique Identifier - * (Informative)" / "UUID Derived UID" - * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part05/sect_B.2.html - * https://stackoverflow.com/a/46316162/881731 - **/ - - std::string uuid = GenerateUuid(); - assert(IsUuid(uuid) && uuid.size() == 36); - - /** - * After removing the four dashes ("-") out of the 36-character - * UUID, we get a large hexadecimal number with 32 characters, - * each of those characters lying in the range [0,16[. The large - * number is thus in the [0,16^32[ = [0,256^16[ range. This number - * has a maximum of 39 decimal digits, as can be seen in Python: - * - * # python -c 'import math; print(math.log(16**32))/math.log(10))' - * 38.531839445 - * - * We now to convert the large hexadecimal number to a decimal - * number with up to 39 digits, remove the leading zeros, then - * prefix it with "2.25." - **/ - - // Remove the dashes - std::string hex = (uuid.substr(0, 8) + - uuid.substr(9, 4) + - uuid.substr(14, 4) + - uuid.substr(19, 4) + - uuid.substr(24, 12)); - assert(hex.size() == 32); - - return "2.25." + LargeHexadecimalToDecimal(hex); - } -} - - - -OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content) -{ - return reinterpret_cast(new Orthanc::Toolbox::LinesIterator(content)); -} - - -bool OrthancLinesIterator_GetLine(std::string& target, - const OrthancLinesIterator* iterator) -{ - if (iterator != NULL) - { - return reinterpret_cast(iterator)->GetLine(target); - } - else - { - return false; - } -} - - -void OrthancLinesIterator_Next(OrthancLinesIterator* iterator) -{ - if (iterator != NULL) - { - reinterpret_cast(iterator)->Next(); - } -} - - -void OrthancLinesIterator_Free(OrthancLinesIterator* iterator) -{ - if (iterator != NULL) - { - delete reinterpret_cast(iterator); - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/Toolbox.h --- a/Core/Toolbox.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,292 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "Enumerations.h" -#include "OrthancFramework.h" - -#include -#include -#include -#include - - -#if !defined(ORTHANC_ENABLE_BASE64) -# error The macro ORTHANC_ENABLE_BASE64 must be defined -#endif - -#if !defined(ORTHANC_ENABLE_LOCALE) -# error The macro ORTHANC_ENABLE_LOCALE must be defined -#endif - -#if !defined(ORTHANC_ENABLE_MD5) -# error The macro ORTHANC_ENABLE_MD5 must be defined -#endif - -#if !defined(ORTHANC_ENABLE_PUGIXML) -# error The macro ORTHANC_ENABLE_PUGIXML must be defined -#endif - -#if !defined(ORTHANC_ENABLE_SSL) -# error The macro ORTHANC_ENABLE_SSL must be defined -#endif - - -/** - * NOTE: GUID vs. UUID - * The simple answer is: no difference, they are the same thing. Treat - * them as a 16 byte (128 bits) value that is used as a unique - * value. In Microsoft-speak they are called GUIDs, but call them - * UUIDs when not using Microsoft-speak. - * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid - **/ - - -#if ORTHANC_ENABLE_PUGIXML == 1 -# include -#endif - - -namespace Orthanc -{ - typedef std::vector UriComponents; - - class NullType - { - }; - - class ORTHANC_PUBLIC Toolbox - { - public: - class ORTHANC_PUBLIC LinesIterator - { - private: - const std::string& content_; - size_t lineStart_; - size_t lineEnd_; - - void FindEndOfLine(); - - public: - LinesIterator(const std::string& content); - - bool GetLine(std::string& target) const; - - void Next(); - }; - - static void ToUpperCase(std::string& s); // Inplace version - - static void ToLowerCase(std::string& s); // Inplace version - - static void ToUpperCase(std::string& result, - const std::string& source); - - static void ToLowerCase(std::string& result, - const std::string& source); - - static void SplitUriComponents(UriComponents& components, - const std::string& uri); - - static void TruncateUri(UriComponents& target, - const UriComponents& source, - size_t fromLevel); - - static bool IsChildUri(const UriComponents& baseUri, - const UriComponents& testedUri); - - static std::string FlattenUri(const UriComponents& components, - size_t fromLevel = 0); - -#if ORTHANC_ENABLE_MD5 == 1 - static void ComputeMD5(std::string& result, - const std::string& data); - - static void ComputeMD5(std::string& result, - const void* data, - size_t size); -#endif - - static void ComputeSHA1(std::string& result, - const std::string& data); - - static void ComputeSHA1(std::string& result, - const void* data, - size_t size); - - static bool IsSHA1(const void* str, - size_t size); - - static bool IsSHA1(const std::string& s); - -#if ORTHANC_ENABLE_BASE64 == 1 - static void DecodeBase64(std::string& result, - const std::string& data); - - static void EncodeBase64(std::string& result, - const std::string& data); - - static bool DecodeDataUriScheme(std::string& mime, - std::string& content, - const std::string& source); - - static void EncodeDataUriScheme(std::string& result, - const std::string& mime, - const std::string& content); -#endif - -#if ORTHANC_ENABLE_LOCALE == 1 - static std::string ConvertToUtf8(const std::string& source, - Encoding sourceEncoding, - bool hasCodeExtensions); - - static std::string ConvertFromUtf8(const std::string& source, - Encoding targetEncoding); -#endif - - static bool IsAsciiString(const void* data, - size_t size); - - static bool IsAsciiString(const std::string& s); - - static std::string ConvertToAscii(const std::string& source); - - static std::string StripSpaces(const std::string& source); - - // In-place percent-decoding for URL - static void UrlDecode(std::string& s); - - static Endianness DetectEndianness(); - - static std::string WildcardToRegularExpression(const std::string& s); - - static void TokenizeString(std::vector& result, - const std::string& source, - char separator); - -#if ORTHANC_ENABLE_PUGIXML == 1 - static void JsonToXml(std::string& target, - const Json::Value& source, - const std::string& rootElement = "root", - const std::string& arrayElement = "item"); -#endif - -#if ORTHANC_ENABLE_PUGIXML == 1 - static void XmlToString(std::string& target, - const pugi::xml_document& source); -#endif - - static bool IsInteger(const std::string& str); - - static void CopyJsonWithoutComments(Json::Value& target, - const Json::Value& source); - - static bool StartsWith(const std::string& str, - const std::string& prefix); - - static void UriEncode(std::string& target, - const std::string& source); - - static std::string GetJsonStringField(const ::Json::Value& json, - const std::string& key, - const std::string& defaultValue); - - static bool GetJsonBooleanField(const ::Json::Value& json, - const std::string& key, - bool defaultValue); - - static int GetJsonIntegerField(const ::Json::Value& json, - const std::string& key, - int defaultValue); - - static unsigned int GetJsonUnsignedIntegerField(const ::Json::Value& json, - const std::string& key, - unsigned int defaultValue); - - static bool IsUuid(const std::string& str); - - static bool StartsWithUuid(const std::string& str); - -#if ORTHANC_ENABLE_LOCALE == 1 - static void InitializeGlobalLocale(const char* locale); - - static void FinalizeGlobalLocale(); - - static std::string ToUpperCaseWithAccents(const std::string& source); -#endif - - static void InitializeOpenSsl(); - - static void FinalizeOpenSsl(); - - static std::string GenerateUuid(); - - static std::string SubstituteVariables(const std::string& source, - const std::map& dictionary); - - static void RemoveIso2022EscapeSequences(std::string& dest, - const std::string& src); - - static void Utf8ToUnicodeCharacter(uint32_t& unicode, - size_t& utf8Length, - const std::string& utf8, - size_t position); - - static std::string LargeHexadecimalToDecimal(const std::string& hex); - - // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part05/sect_B.2.html - static std::string GenerateDicomPrivateUniqueIdentifier(); - }; -} - - - - -/** - * The plain C, opaque data structure "OrthancLinesIterator" is a thin - * wrapper around Orthanc::Toolbox::LinesIterator, and is only used by - * "../Resources/Patches/dcmtk-dcdict_orthanc.cc", in order to avoid - * code duplication - **/ - -struct OrthancLinesIterator; - -OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content); - -bool OrthancLinesIterator_GetLine(std::string& target, - const OrthancLinesIterator* iterator); - -void OrthancLinesIterator_Next(OrthancLinesIterator* iterator); - -void OrthancLinesIterator_Free(OrthancLinesIterator* iterator); diff -r 6c6239aec462 -r d25f4c0fa160 Core/WebServiceParameters.cpp --- a/Core/WebServiceParameters.cpp Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,585 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#include "PrecompiledHeaders.h" -#include "WebServiceParameters.h" - -#include "Logging.h" -#include "OrthancException.h" -#include "SerializationToolbox.h" -#include "Toolbox.h" - -#if ORTHANC_SANDBOXED == 0 -# include "SystemToolbox.h" -#endif - -#include - -namespace Orthanc -{ - static const char* KEY_CERTIFICATE_FILE = "CertificateFile"; - static const char* KEY_CERTIFICATE_KEY_FILE = "CertificateKeyFile"; - static const char* KEY_CERTIFICATE_KEY_PASSWORD = "CertificateKeyPassword"; - static const char* KEY_HTTP_HEADERS = "HttpHeaders"; - static const char* KEY_PASSWORD = "Password"; - static const char* KEY_PKCS11 = "Pkcs11"; - static const char* KEY_URL = "Url"; - static const char* KEY_URL_2 = "URL"; - static const char* KEY_USERNAME = "Username"; - - - static bool IsReservedKey(const std::string& key) - { - return (key == KEY_CERTIFICATE_FILE || - key == KEY_CERTIFICATE_KEY_FILE || - key == KEY_CERTIFICATE_KEY_PASSWORD || - key == KEY_HTTP_HEADERS || - key == KEY_PASSWORD || - key == KEY_PKCS11 || - key == KEY_URL || - key == KEY_URL_2 || - key == KEY_USERNAME); - } - - - WebServiceParameters::WebServiceParameters() : - pkcs11Enabled_(false) - { - SetUrl("http://127.0.0.1:8042/"); - } - - - void WebServiceParameters::ClearClientCertificate() - { - certificateFile_.clear(); - certificateKeyFile_.clear(); - certificateKeyPassword_.clear(); - } - - - void WebServiceParameters::SetUrl(const std::string& url) - { - if (!Toolbox::StartsWith(url, "http://") && - !Toolbox::StartsWith(url, "https://")) - { - throw OrthancException(ErrorCode_BadFileFormat, "Bad URL: " + url); - } - - // Add trailing slash if needed - if (url[url.size() - 1] == '/') - { - url_ = url; - } - else - { - url_ = url + '/'; - } - } - - - void WebServiceParameters::ClearCredentials() - { - username_.clear(); - password_.clear(); - } - - - void WebServiceParameters::SetCredentials(const std::string& username, - const std::string& password) - { - if (username.empty() && - !password.empty()) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - username_ = username; - password_ = password; - } - } - - - void WebServiceParameters::SetClientCertificate(const std::string& certificateFile, - const std::string& certificateKeyFile, - const std::string& certificateKeyPassword) - { - if (certificateFile.empty()) - { - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - - if (certificateKeyPassword.empty()) - { - throw OrthancException( - ErrorCode_BadFileFormat, - "The password for the HTTPS certificate is not provided: " + certificateFile); - } - - certificateFile_ = certificateFile; - certificateKeyFile_ = certificateKeyFile; - certificateKeyPassword_ = certificateKeyPassword; - } - - - void WebServiceParameters::FromSimpleFormat(const Json::Value& peer) - { - assert(peer.isArray()); - - pkcs11Enabled_ = false; - ClearClientCertificate(); - - if (peer.size() != 1 && - peer.size() != 3) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - - SetUrl(peer.get(0u, "").asString()); - - if (peer.size() == 1) - { - ClearCredentials(); - } - else if (peer.size() == 2) - { - throw OrthancException(ErrorCode_BadFileFormat, - "The HTTP password is not provided"); - } - else if (peer.size() == 3) - { - SetCredentials(peer.get(1u, "").asString(), - peer.get(2u, "").asString()); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - static std::string GetStringMember(const Json::Value& peer, - const std::string& key, - const std::string& defaultValue) - { - if (!peer.isMember(key)) - { - return defaultValue; - } - else if (peer[key].type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - return peer[key].asString(); - } - } - - - void WebServiceParameters::FromAdvancedFormat(const Json::Value& peer) - { - assert(peer.isObject()); - - std::string url = GetStringMember(peer, KEY_URL, ""); - if (url.empty()) - { - SetUrl(GetStringMember(peer, KEY_URL_2, "")); - } - else - { - SetUrl(url); - } - - SetCredentials(GetStringMember(peer, KEY_USERNAME, ""), - GetStringMember(peer, KEY_PASSWORD, "")); - - std::string file = GetStringMember(peer, KEY_CERTIFICATE_FILE, ""); - if (!file.empty()) - { - SetClientCertificate(file, GetStringMember(peer, KEY_CERTIFICATE_KEY_FILE, ""), - GetStringMember(peer, KEY_CERTIFICATE_KEY_PASSWORD, "")); - } - else - { - ClearClientCertificate(); - } - - if (peer.isMember(KEY_PKCS11)) - { - if (peer[KEY_PKCS11].type() == Json::booleanValue) - { - pkcs11Enabled_ = peer[KEY_PKCS11].asBool(); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - else - { - pkcs11Enabled_ = false; - } - - - headers_.clear(); - - if (peer.isMember(KEY_HTTP_HEADERS)) - { - const Json::Value& h = peer[KEY_HTTP_HEADERS]; - if (h.type() != Json::objectValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - Json::Value::Members keys = h.getMemberNames(); - for (size_t i = 0; i < keys.size(); i++) - { - const Json::Value& value = h[keys[i]]; - if (value.type() != Json::stringValue) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - else - { - headers_[keys[i]] = value.asString(); - } - } - } - } - - - userProperties_.clear(); - - const Json::Value::Members members = peer.getMemberNames(); - - for (Json::Value::Members::const_iterator it = members.begin(); - it != members.end(); ++it) - { - if (!IsReservedKey(*it)) - { - switch (peer[*it].type()) - { - case Json::stringValue: - userProperties_[*it] = peer[*it].asString(); - break; - - case Json::booleanValue: - userProperties_[*it] = peer[*it].asBool() ? "1" : "0"; - break; - - case Json::intValue: - userProperties_[*it] = boost::lexical_cast(peer[*it].asInt()); - break; - - default: - throw OrthancException(ErrorCode_BadFileFormat, - "User-defined properties associated with a Web service must be strings: " + *it); - } - } - } - } - - - void WebServiceParameters::Unserialize(const Json::Value& peer) - { - try - { - if (peer.isArray()) - { - FromSimpleFormat(peer); - } - else if (peer.isObject()) - { - FromAdvancedFormat(peer); - } - else - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - catch (OrthancException&) - { - throw; - } - catch (...) - { - throw OrthancException(ErrorCode_BadFileFormat); - } - } - - - void WebServiceParameters::ListHttpHeaders(std::set& target) const - { - target.clear(); - - for (Dictionary::const_iterator it = headers_.begin(); - it != headers_.end(); ++it) - { - target.insert(it->first); - } - } - - - bool WebServiceParameters::LookupHttpHeader(std::string& value, - const std::string& key) const - { - Dictionary::const_iterator found = headers_.find(key); - - if (found == headers_.end()) - { - return false; - } - else - { - value = found->second; - return true; - } - } - - - void WebServiceParameters::AddUserProperty(const std::string& key, - const std::string& value) - { - if (IsReservedKey(key)) - { - throw OrthancException( - ErrorCode_ParameterOutOfRange, - "Cannot use this reserved key to name an user property: " + key); - } - else - { - userProperties_[key] = value; - } - } - - - void WebServiceParameters::ListUserProperties(std::set& target) const - { - target.clear(); - - for (Dictionary::const_iterator it = userProperties_.begin(); - it != userProperties_.end(); ++it) - { - target.insert(it->first); - } - } - - - bool WebServiceParameters::LookupUserProperty(std::string& value, - const std::string& key) const - { - Dictionary::const_iterator found = userProperties_.find(key); - - if (found == userProperties_.end()) - { - return false; - } - else - { - value = found->second; - return true; - } - } - - - bool WebServiceParameters::GetBooleanUserProperty(const std::string& key, - bool defaultValue) const - { - Dictionary::const_iterator found = userProperties_.find(key); - - if (found == userProperties_.end()) - { - return defaultValue; - } - else if (found->second == "0" || - found->second == "false") - { - return false; - } - else if (found->second == "1" || - found->second == "true") - { - return true; - } - else - { - throw OrthancException(ErrorCode_BadFileFormat, "Bad value for a Boolean user property in the parameters " - "of a Web service: Property \"" + key + "\" equals: " + found->second); - } - } - - - bool WebServiceParameters::IsAdvancedFormatNeeded() const - { - return (!certificateFile_.empty() || - !certificateKeyFile_.empty() || - !certificateKeyPassword_.empty() || - pkcs11Enabled_ || - !headers_.empty() || - !userProperties_.empty()); - } - - - void WebServiceParameters::Serialize(Json::Value& value, - bool forceAdvancedFormat, - bool includePasswords) const - { - if (forceAdvancedFormat || - IsAdvancedFormatNeeded()) - { - value = Json::objectValue; - value[KEY_URL] = url_; - - if (!username_.empty() || - !password_.empty()) - { - value[KEY_USERNAME] = username_; - - if (includePasswords) - { - value[KEY_PASSWORD] = password_; - } - } - - if (!certificateFile_.empty()) - { - value[KEY_CERTIFICATE_FILE] = certificateFile_; - } - - if (!certificateKeyFile_.empty()) - { - value[KEY_CERTIFICATE_KEY_FILE] = certificateKeyFile_; - } - - if (!certificateKeyPassword_.empty() && - includePasswords) - { - value[KEY_CERTIFICATE_KEY_PASSWORD] = certificateKeyPassword_; - } - - value[KEY_PKCS11] = pkcs11Enabled_; - - value[KEY_HTTP_HEADERS] = Json::objectValue; - for (Dictionary::const_iterator it = headers_.begin(); - it != headers_.end(); ++it) - { - value[KEY_HTTP_HEADERS][it->first] = it->second; - } - - for (Dictionary::const_iterator it = userProperties_.begin(); - it != userProperties_.end(); ++it) - { - value[it->first] = it->second; - } - } - else - { - value = Json::arrayValue; - value.append(url_); - - if (!username_.empty() || - !password_.empty()) - { - value.append(username_); - value.append(includePasswords ? password_ : ""); - } - } - } - - -#if ORTHANC_SANDBOXED == 0 - void WebServiceParameters::CheckClientCertificate() const - { - if (!certificateFile_.empty()) - { - if (!SystemToolbox::IsRegularFile(certificateFile_)) - { - throw OrthancException(ErrorCode_InexistentFile, - "Cannot open certificate file: " + certificateFile_); - } - - if (!certificateKeyFile_.empty() && - !SystemToolbox::IsRegularFile(certificateKeyFile_)) - { - throw OrthancException(ErrorCode_InexistentFile, - "Cannot open key file: " + certificateKeyFile_); - } - } - } -#endif - - - void WebServiceParameters::FormatPublic(Json::Value& target) const - { - target = Json::objectValue; - - // Only return the public information identifying the destination. - // "Security"-related information such as passwords and HTTP - // headers are shown as "null" values. - target[KEY_URL] = url_; - - if (!username_.empty()) - { - target[KEY_USERNAME] = username_; - target[KEY_PASSWORD] = Json::nullValue; - } - - if (!certificateFile_.empty()) - { - target[KEY_CERTIFICATE_FILE] = certificateFile_; - target[KEY_CERTIFICATE_KEY_FILE] = Json::nullValue; - target[KEY_CERTIFICATE_KEY_PASSWORD] = Json::nullValue; - } - - target[KEY_PKCS11] = pkcs11Enabled_; - - Json::Value headers = Json::arrayValue; - - for (Dictionary::const_iterator it = headers_.begin(); - it != headers_.end(); ++it) - { - // Only list the HTTP headers, not their value - headers.append(it->first); - } - - target[KEY_HTTP_HEADERS] = headers; - - for (Dictionary::const_iterator it = userProperties_.begin(); - it != userProperties_.end(); ++it) - { - target[it->first] = it->second; - } - } -} diff -r 6c6239aec462 -r d25f4c0fa160 Core/WebServiceParameters.h --- a/Core/WebServiceParameters.h Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2020 Osimis S.A., Belgium - * - * This program is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * In addition, as a special exception, the copyright holders of this - * program give permission to link the code of its release with the - * OpenSSL project's "OpenSSL" library (or with modified versions of it - * that use the same license as the "OpenSSL" library), and distribute - * the linked executables. You must obey the GNU General Public License - * in all respects for all of the code used other than "OpenSSL". If you - * modify file(s) with this exception, you may extend this exception to - * your version of the file(s), but you are not obligated to do so. If - * you do not wish to do so, delete this exception statement from your - * version. If you delete this exception statement from all source files - * in the program, then also delete it here. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - **/ - - -#pragma once - -#include "OrthancFramework.h" - -#if !defined(ORTHANC_SANDBOXED) -# error The macro ORTHANC_SANDBOXED must be defined -#endif - -#include -#include -#include -#include - -namespace Orthanc -{ - class ORTHANC_PUBLIC WebServiceParameters - { - public: - typedef std::map Dictionary; - - private: - std::string url_; - std::string username_; - std::string password_; - std::string certificateFile_; - std::string certificateKeyFile_; - std::string certificateKeyPassword_; - bool pkcs11Enabled_; - Dictionary headers_; - Dictionary userProperties_; - - void FromSimpleFormat(const Json::Value& peer); - - void FromAdvancedFormat(const Json::Value& peer); - - public: - WebServiceParameters(); - - WebServiceParameters(const Json::Value& serialized) - { - Unserialize(serialized); - } - - const std::string& GetUrl() const - { - return url_; - } - - void SetUrl(const std::string& url); - - void ClearCredentials(); - - void SetCredentials(const std::string& username, - const std::string& password); - - const std::string& GetUsername() const - { - return username_; - } - - const std::string& GetPassword() const - { - return password_; - } - - void ClearClientCertificate(); - - void SetClientCertificate(const std::string& certificateFile, - const std::string& certificateKeyFile, - const std::string& certificateKeyPassword); - - const std::string& GetCertificateFile() const - { - return certificateFile_; - } - - const std::string& GetCertificateKeyFile() const - { - return certificateKeyFile_; - } - - const std::string& GetCertificateKeyPassword() const - { - return certificateKeyPassword_; - } - - void SetPkcs11Enabled(bool enabled) - { - pkcs11Enabled_ = enabled; - } - - bool IsPkcs11Enabled() const - { - return pkcs11Enabled_; - } - - void AddHttpHeader(const std::string& key, - const std::string& value) - { - headers_[key] = value; - } - - void ClearHttpHeaders() - { - headers_.clear(); - } - - const Dictionary& GetHttpHeaders() const - { - return headers_; - } - - void ListHttpHeaders(std::set& target) const; - - bool LookupHttpHeader(std::string& value, - const std::string& key) const; - - void AddUserProperty(const std::string& key, - const std::string& value); - - void ClearUserProperties() - { - userProperties_.clear(); - } - - const Dictionary& GetUserProperties() const - { - return userProperties_; - } - - void ListUserProperties(std::set& target) const; - - bool LookupUserProperty(std::string& value, - const std::string& key) const; - - bool GetBooleanUserProperty(const std::string& key, - bool defaultValue) const; - - bool IsAdvancedFormatNeeded() const; - - void Unserialize(const Json::Value& peer); - - void Serialize(Json::Value& value, - bool forceAdvancedFormat, - bool includePasswords) const; - -#if ORTHANC_SANDBOXED == 0 - void CheckClientCertificate() const; -#endif - - void FormatPublic(Json::Value& target) const; - }; -} diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/explorer.css --- a/OrthancExplorer/explorer.css Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -ul.tree ul { - margin-left: 36px; -} - -#progress { - position: relative; - /*height: 2em; */ - width: 100%; - background-color: grey; - height: 2.5em; -} - -#progress .label { - z-index: 10; - position: absolute; - left:0; - top: 0; - width: 100%; - font-weight: bold; - text-align: center; - text-shadow: none; - padding: .5em; - color: white; -} - -#progress .bar { - z-index: 0; - position: absolute; - left:0; - top: 0; - height: 100%; - width: 0%; - background-color: green; -} - -.ui-title a { - text-decoration: none; - color: white !important; -} - -.switch-container .ui-slider-switch { - width: 100%; -} \ No newline at end of file diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/explorer.html --- a/OrthancExplorer/explorer.html Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,665 +0,0 @@ - - - - - - - - Orthanc Explorer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-

Lookup studies

-
- Lookup - Plugins -
- -
-
-
-

- - Orthanc - -

-
- -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
-
-
 
-
-
-
-
-

Warning:

Your lookup led to many results! - Showing only ? studies to - avoid performance issue. Please make your query more - specific, then relaunch the lookup. -
-
 
-
-
    -
-
-
-
- -
-
-

All patients

-
- Lookup - Plugins -
- -
-
-
-
-

Warning:

This is a large Orthanc server. Showing - only ? patients to avoid - performance issue. Make sure to use lookup if targeting - specific patients! -
-
 
-
-
    -
-
-
- -
-
-

All studies

-
- Lookup - Plugins -
- -
-
-
-
-

Warning:

This is a large Orthanc server. Showing - only ? studies to avoid - performance issue. Make sure to use lookup if targeting - specific studies! -
-
 
-
-
    -
-
-
- -
-
-

Upload DICOM files

-
- Lookup - Plugins -
-
-
-
- - -
-

-

-
- -
-
-

-
-

Warning:

Orthanc issue #21: On Firefox, especially on - Linux & OSX systems, files might be missing if using - drag-and-drop. Please use the "Select files to upload" button - instead, or use the command-line "ImportDicomFiles.py" script. -
-
    -
  • Drag and drop DICOM files here
  • -
-
-
- -
-
-

Patient

-
- Lookup - Plugins -
- -
-
-
-
-
-
    -
-

-

- -
-

- - - -
-
-
-
-
    -
-
-
-
-
-
- -
-
-

- - Patient » - Study -

-
- Lookup - Plugins -
- -
-
-
- -
-
-
    -
-
-
-
-
-
- -
-
-

- - Patient » - Study » - Series -

-
- Lookup - Plugins -
- -
-
-
- -
-
-
    -
-
-
-
-
-
- -
-
-

- - Patient » - Study » - Series » - Instance -

-
- Lookup - Plugins -
- -
-
-
- -
-
-
-

DICOM Tags

-

- - -

-
-
-
-
-
-
-
- -
-
-

Plugins

-
- Lookup - Plugins -
-
-
-
    -
-
-
- -
-
-

DICOM Query/Retrieve (1/4)

-
- Lookup - Plugins -
-
-
-
-
- - -
- -
-
- Field of interest: - - - - - - - - -
-
- -
- - -
- -
- - -
- -
-
-
- Modalities: - - - - - - - - - - -
-
-
- -
-
- -
-
- -
-
-
-
-
- - -
-
-

DICOM Query/Retrieve (2/4)

-
- Lookup - Plugins -
- Query/Retrieve -
-
-
    -
-
-
- - -
-
-

DICOM Query/Retrieve (3/4)

-
- Lookup - Plugins -
- Query/Retrieve -
-
-
    -
-
-
- - -
-
-

DICOM Query/Retrieve (4/4)

-
- Lookup - Plugins -
- Query/Retrieve -
- -
-
-
- - -
- -
-
-
- -
-
-
-
-
-
- - -
-
-

Jobs

-
- Lookup - Plugins -
-
-
-
    -
-
-
- -
-
-

Job

-
- Lookup - Plugins -
-
- Jobs -
-
-
-
    -
- -
-
-
- - - - -
-
-
-
-
- - - - - - - - - - - - - diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/explorer.js --- a/OrthancExplorer/explorer.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1570 +0,0 @@ -// http://stackoverflow.com/questions/1663741/is-there-a-good-jquery-drag-and-drop-file-upload-plugin - - -// Forbid the access to IE -if ($.browser.msie) -{ - alert("Please use Mozilla Firefox or Google Chrome. Microsoft Internet Explorer is not supported."); -} - -// http://jquerymobile.com/demos/1.1.0/docs/api/globalconfig.html -//$.mobile.ajaxEnabled = false; -//$.mobile.page.prototype.options.addBackBtn = true; -//$.mobile.defaultPageTransition = 'slide'; - - -var LIMIT_RESOURCES = 100; - -var currentPage = ''; -var currentUuid = ''; - - -function DeepCopy(obj) -{ - return jQuery.extend(true, {}, obj); -} - - -function ChangePage(page, options) -{ - var first = true; - var value; - - if (options) { - for (var key in options) { - value = options[key]; - if (first) { - page += '?'; - first = false; - } else { - page += '&'; - } - - page += key + '=' + value; - } - } - - window.location.replace('explorer.html#' + page); - /*$.mobile.changePage('#' + page, { - changeHash: true - });*/ -} - - -function Refresh() -{ - if (currentPage == 'patient') - RefreshPatient(); - else if (currentPage == 'study') - RefreshStudy(); - else if (currentPage == 'series') - RefreshSeries(); - else if (currentPage == 'instance') - RefreshInstance(); -} - - -$(document).ready(function() { - var $tree = $('#dicom-tree'); - $tree.tree({ - autoEscape: false - }); - - $('#dicom-tree').bind( - 'tree.click', - function(event) { - if (event.node.is_open) - $tree.tree('closeNode', event.node, true); - else - $tree.tree('openNode', event.node, true); - } - ); - - // Inject the template of the warning about insecure setup as the - // first child of each page - var insecure = $('#template-insecure').html(); - $('[data-role="page"]>[data-role="content"]').prepend(insecure); - - currentPage = $.mobile.pageData.active; - currentUuid = $.mobile.pageData.uuid; - if (!(typeof currentPage === 'undefined') && - !(typeof currentUuid === 'undefined') && - currentPage.length > 0 && - currentUuid.length > 0) - { - Refresh(); - } -}); - -function GetAuthorizationTokensFromUrl() { - var urlVariables = window.location.search.substring(1).split('&'); - var dict = {}; - - for (var i = 0; i < urlVariables.length; i++) { - var split = urlVariables[i].split('='); - - if (split.length == 2 && (split[0] == "token" || split[0] == "auth-token" || split[0] == "authorization")) { - dict[split[0]] = split[1]; - } - } - return dict; -}; - -var authorizationTokens = GetAuthorizationTokensFromUrl(); - -/* Copy the authoziation toekn from the url search parameters into HTTP headers in every request to the Rest API. -Thanks to this behaviour, you may specify a ?token=xxx in your url and this will be passed -as the "token" header in every request to the API allowing you to use the authorization plugin */ -$.ajaxSetup( - { - headers : authorizationTokens - } -); - - -function SplitLongUid(s) -{ - return '' + s.substr(0, s.length / 2) + ' ' + s.substr(s.length / 2, s.length - s.length / 2) + ''; -} - - -function ParseDicomDate(s) -{ - y = parseInt(s.substr(0, 4), 10); - m = parseInt(s.substr(4, 2), 10) - 1; - d = parseInt(s.substr(6, 2), 10); - - if (y == null || m == null || d == null || - !isFinite(y) || !isFinite(m) || !isFinite(d)) - { - return null; - } - - if (y < 1900 || y > 2100 || - m < 0 || m >= 12 || - d <= 0 || d >= 32) - { - return null; - } - - return new Date(y, m, d); -} - - -function FormatDicomDate(s) -{ - if (s == undefined) - return "No date"; - - var d = ParseDicomDate(s); - if (d == null) - return '?'; - else - return d.toString('dddd, MMMM d, yyyy'); -} - -function FormatFloatSequence(s) -{ - if (s == undefined || s.length == 0) - return "-"; - - if (s.indexOf("\\") == -1) - return s; - - var oldValues = s.split("\\"); - var newValues = []; - for (var i = 0; i < oldValues.length; i++) - { - newValues.push(parseFloat(oldValues[i]).toFixed(3)); - } - return newValues.join("\\"); -} - -function Sort(arr, fieldExtractor, isInteger, reverse) -{ - var defaultValue; - if (isInteger) - defaultValue = 0; - else - defaultValue = ''; - - arr.sort(function(a, b) { - var ta = fieldExtractor(a); - var tb = fieldExtractor(b); - var order; - - if (ta == undefined) - ta = defaultValue; - - if (tb == undefined) - tb = defaultValue; - - if (isInteger) - { - ta = parseInt(ta, 10); - tb = parseInt(tb, 10); - order = ta - tb; - } - else - { - if (ta < tb) - order = -1; - else if (ta > tb) - order = 1; - else - order = 0; - } - - if (reverse) - return -order; - else - return order; - }); -} - - -function SortOnDicomTag(arr, tag, isInteger, reverse) -{ - return Sort(arr, function(a) { - return a.MainDicomTags[tag]; - }, isInteger, reverse); -} - - - -function GetResource(uri, callback) -{ - $.ajax({ - url: '..' + uri, - dataType: 'json', - async: false, - cache: false, - success: function(s) { - callback(s); - } - }); -} - - -function CompleteFormatting(node, link, isReverse, count) -{ - if (count != null) - { - node = node.add($('') - .addClass('ui-li-count') - .text(count)); - } - - if (link != null) - { - node = $('').attr('href', link).append(node); - - if (isReverse) - node.attr('data-direction', 'reverse') - } - - node = $('
  • ').append(node); - - if (isReverse) - node.attr('data-icon', 'back'); - - return node; -} - - -function FormatMainDicomTags(target, tags, tagsToIgnore) -{ - var v; - - for (var i in tags) - { - if (tagsToIgnore.indexOf(i) == -1) - { - v = tags[i]; - - if (i == "PatientBirthDate" || - i == "StudyDate" || - i == "SeriesDate") - { - v = FormatDicomDate(v); - } - else if (i == "DicomStudyInstanceUID" || - i == "DicomSeriesInstanceUID") - { - v = SplitLongUid(v); - } - else if (i == "ImagePositionPatient" || - i == "ImageOrientationPatient") - { - v = FormatFloatSequence(v); - } - - target.append($('

    ') - .text(i + ': ') - .append($('').text(v))); - } - } -} - - -function FormatPatient(patient, link, isReverse) -{ - var node = $('

    ').append($('

    ').text(patient.MainDicomTags.PatientName)); - - FormatMainDicomTags(node, patient.MainDicomTags, [ - "PatientName" - // "OtherPatientIDs" - ]); - - return CompleteFormatting(node, link, isReverse, patient.Studies.length); -} - - - -function FormatStudy(study, link, isReverse, includePatient) -{ - var label; - var node; - - if (includePatient) { - label = study.Label; - } else { - label = study.MainDicomTags.StudyDescription; - } - - node = $('
    ').append($('

    ').text(label)); - - if (includePatient) { - FormatMainDicomTags(node, study.PatientMainDicomTags, [ - 'PatientName' - ]); - } - - FormatMainDicomTags(node, study.MainDicomTags, [ - 'StudyDescription', - 'StudyTime' - ]); - - return CompleteFormatting(node, link, isReverse, study.Series.length); -} - - - -function FormatSeries(series, link, isReverse) -{ - var c; - var node; - - if (series.ExpectedNumberOfInstances == null || - series.Instances.length == series.ExpectedNumberOfInstances) - { - c = series.Instances.length; - } - else - { - c = series.Instances.length + '/' + series.ExpectedNumberOfInstances; - } - - node = $('
    ') - .append($('

    ').text(series.MainDicomTags.SeriesDescription)) - .append($('

    ').append($('') - .text('Status: ') - .append($('').text(series.Status)))); - - FormatMainDicomTags(node, series.MainDicomTags, [ - "SeriesDescription", - "SeriesTime", - "Manufacturer", - "ImagesInAcquisition", - "SeriesDate", - "ImageOrientationPatient" - ]); - - return CompleteFormatting(node, link, isReverse, c); -} - - -function FormatInstance(instance, link, isReverse) -{ - var node = $('

    ').append($('

    ').text('Instance: ' + instance.IndexInSeries)); - - FormatMainDicomTags(node, instance.MainDicomTags, [ - "AcquisitionNumber", - "InstanceNumber", - "InstanceCreationDate", - "InstanceCreationTime" - ]); - - return CompleteFormatting(node, link, isReverse); -} - - -$('[data-role="page"]').live('pagebeforeshow', function() { - $.ajax({ - url: '../system', - dataType: 'json', - async: false, - cache: false, - success: function(s) { - if (s.Name != "") { - $('.orthanc-name').html($('') - .addClass('ui-link') - .attr('href', 'explorer.html') - .text(s.Name) - .append(' » ')); - } - - // New in Orthanc 1.5.8 - if ('IsHttpServerSecure' in s && - !s.IsHttpServerSecure) { - $('.warning-insecure').show(); - } else { - $('.warning-insecure').hide(); - } - } - }); -}); - - - -$('#lookup').live('pagebeforeshow', function() { - // NB: "GenerateDicomDate()" is defined in "query-retrieve.js" - var target = $('#lookup-study-date'); - $('option', target).remove(); - target.append($('
      ') - .attr('data-divider-theme', 'd') - .attr('data-role', 'listview'); - - // Retrieve the list of the known DICOM modalities - $.ajax({ - url: '../modalities', - type: 'GET', - dataType: 'json', - async: false, - cache: false, - success: function(modalities) { - var name, item; - - if (modalities.length > 0) - { - items.append('
    • DICOM modalities
    • '); - - for (var i = 0; i < modalities.length; i++) { - name = modalities[i]; - item = $('
    • ') - .html('' + name + '') - .attr('name', name) - .click(function() { - clickedModality = $(this).attr('name'); - }); - items.append(item); - } - } - - // Retrieve the list of the known Orthanc peers - $.ajax({ - url: '../peers', - type: 'GET', - dataType: 'json', - async: false, - cache: false, - success: function(peers) { - var name, item; - - if (peers.length > 0) - { - items.append('
    • Orthanc peers
    • '); - - for (var i = 0; i < peers.length; i++) { - name = peers[i]; - item = $('
    • ') - .html('' + name + '') - .attr('name', name) - .click(function() { - clickedPeer = $(this).attr('name'); - }); - items.append(item); - } - } - - // Launch the dialog - $('#dialog').simpledialog2({ - mode: 'blank', - animate: false, - headerText: 'Choose target', - headerClose: true, - forceInput: false, - width: '100%', - blankContent: items, - callbackClose: function() { - var timer; - function WaitForDialogToClose() { - if (!$('#dialog').is(':visible')) { - clearInterval(timer); - callback(clickedModality, clickedPeer); - } - } - timer = setInterval(WaitForDialogToClose, 100); - } - }); - } - }); - } - }); -} - - -$('#instance-store,#series-store,#study-store,#patient-store').live('click', function(e) { - ChooseDicomModality(function(modality, peer) { - var pageData = DeepCopy($.mobile.pageData); - var url, loading; - - if (modality != '') - { - url = '../modalities/' + modality + '/store'; - loading = '#dicom-store'; - } - - if (peer != '') - { - url = '../peers/' + peer + '/store'; - loading = '#peer-store'; - } - - if (url != '') { - $.ajax({ - url: url, - type: 'POST', - dataType: 'text', - data: pageData.uuid, - async: true, // Necessary to block UI - beforeSend: function() { - $.blockUI({ message: $(loading) }); - }, - complete: function(s) { - $.unblockUI(); - }, - success: function(s) { - }, - error: function() { - alert('Error during store'); - } - }); - } - }); -}); - - -$('#show-tag-name').live('change', function(e) { - var checked = e.currentTarget.checked; - if (checked) - $('.tag-name').show(); - else - $('.tag-name').hide(); -}); - - -$('#patient-archive').live('click', function(e) { - e.preventDefault(); //stop the browser from following - window.location.href = '../patients/' + $.mobile.pageData.uuid + '/archive'; -}); - -$('#study-archive').live('click', function(e) { - e.preventDefault(); //stop the browser from following - window.location.href = '../studies/' + $.mobile.pageData.uuid + '/archive'; -}); - -$('#series-archive').live('click', function(e) { - e.preventDefault(); //stop the browser from following - window.location.href = '../series/' + $.mobile.pageData.uuid + '/archive'; -}); - - -$('#patient-media').live('click', function(e) { - e.preventDefault(); //stop the browser from following - window.location.href = '../patients/' + $.mobile.pageData.uuid + '/media'; -}); - -$('#study-media').live('click', function(e) { - e.preventDefault(); //stop the browser from following - window.location.href = '../studies/' + $.mobile.pageData.uuid + '/media'; -}); - -$('#series-media').live('click', function(e) { - e.preventDefault(); //stop the browser from following - window.location.href = '../series/' + $.mobile.pageData.uuid + '/media'; -}); - - - -$('#protection').live('change', function(e) { - var isProtected = e.target.value == "on"; - $.ajax({ - url: '../patients/' + $.mobile.pageData.uuid + '/protected', - type: 'PUT', - dataType: 'text', - data: isProtected ? '1' : '0', - async: false - }); -}); - - - -function OpenAnonymizeResourceDialog(path, title) -{ - $(document).simpledialog2({ - mode: 'button', - animate: false, - headerText: title, - headerClose: true, - width: '500px', - buttons : { - 'OK': { - click: function () { - $.ajax({ - url: path + '/anonymize', - type: 'POST', - data: '{ "Keep" : [ "SeriesDescription", "StudyDescription" ] }', - dataType: 'json', - async: false, - cache: false, - success: function(s) { - // The following line does not work... - //$.mobile.changePage('explorer.html#patient?uuid=' + s.PatientID); - - window.location.assign('explorer.html#patient?uuid=' + s.PatientID); - //window.location.reload(); - } - }); - }, - icon: "delete", - theme: "c" - }, - 'Cancel': { - click: function () { - } - } - } - }); -} - -$('#instance-anonymize').live('click', function() { - OpenAnonymizeResourceDialog('../instances/' + $.mobile.pageData.uuid, - 'Anonymize this instance?'); -}); - -$('#study-anonymize').live('click', function() { - OpenAnonymizeResourceDialog('../studies/' + $.mobile.pageData.uuid, - 'Anonymize this study?'); -}); - -$('#series-anonymize').live('click', function() { - OpenAnonymizeResourceDialog('../series/' + $.mobile.pageData.uuid, - 'Anonymize this series?'); -}); - -$('#patient-anonymize').live('click', function() { - OpenAnonymizeResourceDialog('../patients/' + $.mobile.pageData.uuid, - 'Anonymize this patient?'); -}); - - -$('#plugins').live('pagebeforeshow', function() { - $.ajax({ - url: '../plugins', - dataType: 'json', - async: false, - cache: false, - success: function(plugins) { - var target = $('#all-plugins'); - $('li', target).remove(); - - plugins.map(function(id) { - return $.ajax({ - url: '../plugins/' + id, - dataType: 'json', - async: false, - cache: false, - success: function(plugin) { - var li = $('
    • '); - var item = li; - - if ('RootUri' in plugin) - { - item = $(''); - li.append(item); - item.click(function() { - window.open(plugin.RootUri); - }); - } - - item.append($('

      ').text(plugin.ID)); - item.append($('

      ').text(plugin.Description)); - item.append($('').addClass('ui-li-count').text(plugin.Version)); - target.append(li); - } - }); - }); - - target.listview('refresh'); - } - }); -}); - - - -function ParseJobTime(s) -{ - var t = (s.substr(0, 4) + '-' + - s.substr(4, 2) + '-' + - s.substr(6, 5) + ':' + - s.substr(11, 2) + ':' + - s.substr(13)); - var utc = new Date(t); - - // Convert from UTC to local time - return new Date(utc.getTime() - utc.getTimezoneOffset() * 60000); -} - - -function AddJobField(target, description, field) -{ - if (!(typeof field === 'undefined')) { - target.append($('

      ') - .text(description) - .append($('').text(field))); - } -} - - -function AddJobDateField(target, description, field) -{ - if (!(typeof field === 'undefined')) { - target.append($('

      ') - .text(description) - .append($('').text(ParseJobTime(field)))); - } -} - - -$('#jobs').live('pagebeforeshow', function() { - $.ajax({ - url: '../jobs?expand', - dataType: 'json', - async: false, - cache: false, - success: function(jobs) { - var target = $('#all-jobs'); - var running, pending, inactive; - - $('li', target).remove(); - - running = $('

    • ') - .attr('data-role', 'list-divider') - .text('Currently running'); - - pending = $('
    • ') - .attr('data-role', 'list-divider') - .text('Pending jobs'); - - inactive = $('
    • ') - .attr('data-role', 'list-divider') - .text('Inactive jobs'); - - target.append(running); - target.append(pending); - target.append(inactive); - - jobs.map(function(job) { - var li = $('
    • '); - var item = $(''); - - li.append(item); - item.attr('href', '#job?uuid=' + job.ID); - item.append($('

      ').text(job.Type)); - item.append($('').addClass('ui-li-count').text(job.State)); - AddJobField(item, 'ID: ', job.ID); - AddJobField(item, 'Local AET: ', job.Content.LocalAet); - AddJobField(item, 'Remote AET: ', job.Content.RemoteAet); - AddJobDateField(item, 'Creation time: ', job.CreationTime); - AddJobDateField(item, 'Completion time: ', job.CompletionTime); - AddJobDateField(item, 'ETA: ', job.EstimatedTimeOfArrival); - - if (job.State == 'Running' || - job.State == 'Pending' || - job.State == 'Paused') { - AddJobField(item, 'Priority: ', job.Priority); - AddJobField(item, 'Progress: ', job.Progress); - } - - if (job.State == 'Running') { - li.insertAfter(running); - } else if (job.State == 'Pending' || - job.State == 'Paused') { - li.insertAfter(pending); - } else { - li.insertAfter(inactive); - } - }); - - target.listview('refresh'); - } - }); -}); - - -$('#job').live('pagebeforeshow', function() { - var pageData, target; - - if ($.mobile.pageData) { - pageData = DeepCopy($.mobile.pageData); - - $.ajax({ - url: '../jobs/' + pageData.uuid, - dataType: 'json', - async: false, - cache: false, - success: function(job) { - var block, value; - - target = $('#job-info'); - $('li', target).remove(); - - target.append($('
    • ') - .attr('data-role', 'list-divider') - .text('General information about the job')); - - { - block = $('
    • '); - for (var i in job) { - if (i == 'CreationTime' || - i == 'CompletionTime' || - i == 'EstimatedTimeOfArrival') { - AddJobDateField(block, i + ': ', job[i]); - } else if (i != 'InternalContent' && - i != 'Content' && - i != 'Timestamp') { - AddJobField(block, i + ': ', job[i]); - } - } - } - - target.append(block); - - target.append($('
    • ') - .attr('data-role', 'list-divider') - .text('Detailed information')); - - { - block = $('
    • '); - - for (var item in job.Content) { - var value = job.Content[item]; - if (typeof value !== 'string') { - value = JSON.stringify(value); - } - - AddJobField(block, item + ': ', value); - } - } - - target.append(block); - - target.listview('refresh'); - - $('#job-cancel').closest('.ui-btn').hide(); - $('#job-resubmit').closest('.ui-btn').hide(); - $('#job-pause').closest('.ui-btn').hide(); - $('#job-resume').closest('.ui-btn').hide(); - - if (job.State == 'Running' || - job.State == 'Pending' || - job.State == 'Retry') { - $('#job-cancel').closest('.ui-btn').show(); - $('#job-pause').closest('.ui-btn').show(); - } - else if (job.State == 'Success') { - } - else if (job.State == 'Failure') { - $('#job-resubmit').closest('.ui-btn').show(); - } - else if (job.State == 'Paused') { - $('#job-resume').closest('.ui-btn').show(); - } - } - }); - } -}); - - - -function TriggerJobAction(action) -{ - $.ajax({ - url: '../jobs/' + $.mobile.pageData.uuid + '/' + action, - type: 'POST', - async: false, - cache: false, - complete: function(s) { - window.location.reload(); - } - }); -} - -$('#job-cancel').live('click', function() { - TriggerJobAction('cancel'); -}); - -$('#job-resubmit').live('click', function() { - TriggerJobAction('resubmit'); -}); - -$('#job-pause').live('click', function() { - TriggerJobAction('pause'); -}); - -$('#job-resume').live('click', function() { - TriggerJobAction('resume'); -}); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/file-upload.js --- a/OrthancExplorer/file-upload.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -var pendingUploads = []; -var currentUpload = 0; -var totalUpload = 0; -var alreadyInitialized = false; // trying to debug Orthanc issue #1 - -$(document).ready(function() { - if (alreadyInitialized) { - console.log("Orthanc issue #1: the fileupload has been initialized twice !"); - } else { - alreadyInitialized = true; - } - - // Initialize the jQuery File Upload widget: - $('#fileupload').fileupload({ - //dataType: 'json', - //maxChunkSize: 500, - //sequentialUploads: true, - limitConcurrentUploads: 3, - add: function (e, data) { - pendingUploads.push(data); - } - }) - .bind('fileuploadstop', function(e, data) { - $('#upload-button').removeClass('ui-disabled'); - //$('#upload-abort').addClass('ui-disabled'); - $('#progress .bar').css('width', '100%'); - if ($('#progress .label').text() != 'Failure') - $('#progress .label').text('Done'); - }) - .bind('fileuploadfail', function(e, data) { - $('#progress .bar') - .css('width', '100%') - .css('background-color', 'red'); - $('#progress .label').text('Failure'); - }) - .bind('fileuploaddrop', function (e, data) { - console.log("dropped " + data.files.length + " files: ", data); - appendFilesToUploadList(data.files); - }) - .bind('fileuploadsend', function (e, data) { - // Update the progress bar. Note: for some weird reason, the - // "fileuploadprogressall" does not work under Firefox. - var progress = parseInt(currentUpload / totalUploads * 100, 10); - currentUpload += 1; - $('#progress .label').text('Uploading: ' + progress + '%'); - $('#progress .bar') - .css('width', progress + '%') - .css('background-color', 'green'); - }); -}); - -function appendFilesToUploadList(files) { - var target = $('#upload-list'); - $.each(files, function (index, file) { - target.append('
    • ' + file.name + '
    • '); - }); - target.listview('refresh'); -} - -$('#fileupload').live('change', function (e) { - appendFilesToUploadList(e.target.files); -}) - - -function ClearUploadProgress() -{ - $('#progress .label').text(''); - $('#progress .bar').css('width', '0%').css('background-color', '#333'); -} - -$('#upload').live('pagebeforeshow', function() { - if (navigator.userAgent.toLowerCase().indexOf('firefox') == -1) { - $("#issue-21-warning").css('display', 'none'); - } - - ClearUploadProgress(); -}); - -$('#upload').live('pageshow', function() { - $('#fileupload').fileupload('enable'); -}); - -$('#upload').live('pagehide', function() { - $('#fileupload').fileupload('disable'); -}); - - -$('#upload-button').live('click', function() { - var pu = pendingUploads; - pendingUploads = []; - - $('.pending-file').remove(); - $('#upload-list').listview('refresh'); - ClearUploadProgress(); - - currentUpload = 1; - totalUploads = pu.length + 1; - if (pu.length > 0) { - $('#upload-button').addClass('ui-disabled'); - //$('#upload-abort').removeClass('ui-disabled'); - } - - for (var i = 0; i < pu.length; i++) { - pu[i].submit(); - } -}); - -$('#upload-clear').live('click', function() { - pendingUploads = []; - $('.pending-file').remove(); - $('#upload-list').listview('refresh'); -}); - -/*$('#upload-abort').live('click', function() { - $('#fileupload').fileupload().abort(); - });*/ diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/images/unsupported.png Binary file OrthancExplorer/images/unsupported.png has changed diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/date.js --- a/OrthancExplorer/libs/date.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -/** - * Version: 1.0 Alpha-1 - * Build Date: 13-Nov-2007 - * Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved. - * License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. - * Website: http://www.datejs.com/ or http://www.coolite.com/datejs/ - */ -Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}}; -Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;idate)?1:(this=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;} -var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);} -if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);} -if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);} -if(x.hour||x.hours){this.addHours(x.hour||x.hours);} -if(x.month||x.months){this.addMonths(x.month||x.months);} -if(x.year||x.years){this.addYears(x.year||x.years);} -if(x.day||x.days){this.addDays(x.day||x.days);} -return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(valuemax){throw new RangeError(value+" is not a valid value for "+name+".");} -return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;} -if(!x.second&&x.second!==0){x.second=-1;} -if(!x.minute&&x.minute!==0){x.minute=-1;} -if(!x.hour&&x.hour!==0){x.hour=-1;} -if(!x.day&&x.day!==0){x.day=-1;} -if(!x.month&&x.month!==0){x.month=-1;} -if(!x.year&&x.year!==0){x.year=-1;} -if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());} -if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());} -if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());} -if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());} -if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());} -if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());} -if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());} -if(x.timezone){this.setTimezone(x.timezone);} -if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);} -return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;} -var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}} -return w;};Date.prototype.isDST=function(){console.log('isDST');return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();}; -Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;} -return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;} -if(!last&&q[1].length===0){last=true;} -if(!last){var qx=[];for(var j=0;j0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}} -if(rx[1].length1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];} -if(args){for(var i=0,px=args.shift();i2)?n:(n+(((n+2000)Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");} -var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});} -return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;} -for(var i=0;i nor the -// names of its contributors may be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY -// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -(function( $, window, undefined ) { - -// Given a query string, convert all the name/value pairs -// into a property/value object. If a name appears more than -// once in a query string, the value is automatically turned -// into an array. -function queryStringToObject( qstr ) -{ - var result = {}, - nvPairs = ( ( qstr || "" ).replace( /^\?/, "" ).split( /&/ ) ), - i, pair, n, v; - - for ( i = 0; i < nvPairs.length; i++ ) { - var pstr = nvPairs[ i ]; - if ( pstr ) { - pair = pstr.split( /=/ ); - n = pair[ 0 ]; - v = pair[ 1 ]; - if ( result[ n ] === undefined ) { - result[ n ] = v; - } else { - if ( typeof result[ n ] !== "object" ) { - result[ n ] = [ result[ n ] ]; - } - result[ n ].push( v ); - } - } - } - - return result; -} - -// The idea here is to listen for any pagebeforechange notifications from -// jQuery Mobile, and then muck with the toPage and options so that query -// params can be passed to embedded/internal pages. So for example, if a -// changePage() request for a URL like: -// -// http://mycompany.com/myapp/#page-1?foo=1&bar=2 -// -// is made, the page that will actually get shown is: -// -// http://mycompany.com/myapp/#page-1 -// -// The browser's location will still be updated to show the original URL. -// The query params for the embedded page are also added as a property/value -// object on the options object. You can access it from your page notifications -// via data.options.pageData. -$( document ).bind( "pagebeforechange", function( e, data ) { - - // We only want to handle the case where we are being asked - // to go to a page by URL, and only if that URL is referring - // to an internal page by id. - - if ( typeof data.toPage === "string" ) { - var u = $.mobile.path.parseUrl( data.toPage ); - if ( $.mobile.path.isEmbeddedPage( u ) ) { - // The request is for an internal page, if the hash - // contains query (search) params, strip them off the - // toPage URL and then set options.dataUrl appropriately - // so the location.hash shows the originally requested URL - // that hash the query params in the hash. - - var u2 = $.mobile.path.parseUrl( u.hash.replace( /^#/, "" ) ); - if ( u2.search ) { - if ( !data.options.dataUrl ) { - data.options.dataUrl = data.toPage; - } - data.options.pageData = queryStringToObject( u2.search ); - data.toPage = u.hrefNoHash + "#" + u2.pathname; - } - } - } -}); - -})( jQuery, window ); - - - - -// http://stackoverflow.com/a/8295488 -$(document).bind("pagebeforechange", function( event, data ) { - $.mobile.pageData = (data && data.options && data.options.pageData) - ? data.options.pageData - : {}; - - $.mobile.pageData.active = data.toPage[0].id; -}); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jqtree-icons.png Binary file OrthancExplorer/libs/jqtree-icons.png has changed diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jqtree.css --- a/OrthancExplorer/libs/jqtree.css Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -ul.tree { - margin-left: 12px; -} - -ul.tree, -ul.tree ul { - list-style: none outside; - margin-bottom: 0; - padding: 0; -} - -ul.tree ul { - display: block; - margin-left: 12px; - margin-right: 0; -} - -ul.tree li.closed > ul { - display: none; -} - -ul.tree li { - clear: both; -} - -ul.tree .toggler { - background-image: url(jqtree-icons.png); - background-repeat: no-repeat; - background-position: -8px 0; - width: 8px; - height: 8px; - display: block; - position: absolute; - left: -12px; - top: 30%; - text-indent: -9999px; - border-bottom: none; -} - -ul.tree div { - cursor: pointer; -} - -ul.tree .title { - color: #1C4257; - vertical-align: middle; -} - -ul.tree li.folder { - margin-bottom: 4px; -} - -ul.tree li.folder.closed { - margin-bottom: 1px; -} - -ul.tree li.folder .title { - margin-left: 0; -} - -ul.tree .toggler.closed { - background-position: 0 0; -} - -span.tree-dragging { - color: #fff; - background: #000; - opacity: 0.6; - cursor: pointer; - padding: 2px 8px; -} - -ul.tree li.ghost { - position: relative; - z-index: 10; - margin-right: 10px; -} - -ul.tree li.ghost span { - display: block; -} - -ul.tree li.ghost span.circle { - background-image: url(jqtree-icons.png); - background-repeat: no-repeat; - background-position: 0 -8px; - height: 8px; - width: 8px; - position: absolute; - top: -4px; - left: 2px; -} - -ul.tree li.ghost span.line { - background-color: #0000ff; - height: 2px; - padding: 0; - position: absolute; - top: -1px; - left: 10px; - width: 100%; -} - -ul.tree li.ghost.inside { - margin-left: 48px; -} - -ul.tree span.tree-hit { - position: absolute; - display: block; -} - -ul.tree span.border { - position: absolute; - display: block; - left: -2px; - top: 0; - border: solid 2px #0000ff; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - margin: 0; -} - -ul.tree div { - width: 100%; /* todo: why is this in here? */ - *width: auto; /* ie7 fix; issue 41 */ - position: relative; -} - -ul.tree li.selected > div, -ul.tree li.selected > div:hover { - background-color: #97BDD6; - background: -webkit-gradient(linear, left top, left bottom, from(#BEE0F5), to(#89AFCA)); - background: -moz-linear-gradient(top, #BEE0F5, #89AFCA); - background: -ms-linear-gradient(top, #BEE0F5, #89AFCA); - background: -o-linear-gradient(top, #BEE0F5, #89AFCA); - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); -} - -ul.tree .moving > div .title { - outline: dashed 1px #0000ff; -} diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/css/jquery.fileupload-ui.css --- a/OrthancExplorer/libs/jquery-file-upload/css/jquery.fileupload-ui.css Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -@charset 'UTF-8'; -/* - * jQuery File Upload UI Plugin CSS 6.3 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2010, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -.fileinput-button { - position: relative; - overflow: hidden; - float: left; - margin-right: 4px; -} -.fileinput-button input { - position: absolute; - top: 0; - right: 0; - margin: 0; - border: solid transparent; - border-width: 0 0 100px 200px; - opacity: 0; - filter: alpha(opacity=0); - -moz-transform: translate(-300px, 0) scale(4); - direction: ltr; - cursor: pointer; -} -.fileupload-buttonbar .btn, -.fileupload-buttonbar .toggle { - margin-bottom: 5px; -} -.files .progress { - width: 200px; -} -.progress-animated .bar { - background: url(../img/progressbar.gif) !important; - filter: none; -} -.fileupload-loading { - position: absolute; - left: 50%; - width: 128px; - height: 128px; - background: url(../img/loading.gif) center no-repeat; - display: none; -} -.fileupload-processing .fileupload-loading { - display: block; -} - -/* Fix for IE 6: */ -*html .fileinput-button { - line-height: 22px; - margin: 1px -3px 0 0; -} - -/* Fix for IE 7: */ -*+html .fileinput-button { - margin: 1px 0 0 0; -} - -@media (max-width: 480px) { - .files .btn span { - display: none; - } - .files .preview * { - width: 40px; - } - .files .name * { - width: 80px; - display: inline-block; - word-wrap: break-word; - } - .files .progress { - width: 20px; - } - .files .delete { - width: 60px; - } -} diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/css/style.css --- a/OrthancExplorer/libs/jquery-file-upload/css/style.css Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -@charset 'UTF-8'; -/* - * jQuery File Upload Plugin CSS Example 1.0 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2012, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -body{ - padding-top: 60px; -} diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/img/loading.gif Binary file OrthancExplorer/libs/jquery-file-upload/img/loading.gif has changed diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/img/progressbar.gif Binary file OrthancExplorer/libs/jquery-file-upload/img/progressbar.gif has changed diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/js/cors/jquery.postmessage-transport.js --- a/OrthancExplorer/libs/jquery-file-upload/js/cors/jquery.postmessage-transport.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,117 +0,0 @@ -/* - * jQuery postMessage Transport Plugin 1.1 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2011, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/*jslint unparam: true, nomen: true */ -/*global define, window, document */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define(['jquery'], factory); - } else { - // Browser globals: - factory(window.jQuery); - } -}(function ($) { - 'use strict'; - - var counter = 0, - names = [ - 'accepts', - 'cache', - 'contents', - 'contentType', - 'crossDomain', - 'data', - 'dataType', - 'headers', - 'ifModified', - 'mimeType', - 'password', - 'processData', - 'timeout', - 'traditional', - 'type', - 'url', - 'username' - ], - convert = function (p) { - return p; - }; - - $.ajaxSetup({ - converters: { - 'postmessage text': convert, - 'postmessage json': convert, - 'postmessage html': convert - } - }); - - $.ajaxTransport('postmessage', function (options) { - if (options.postMessage && window.postMessage) { - var iframe, - loc = $('
      ').prop('href', options.postMessage)[0], - target = loc.protocol + '//' + loc.host, - xhrUpload = options.xhr().upload; - return { - send: function (_, completeCallback) { - var message = { - id: 'postmessage-transport-' + (counter += 1) - }, - eventName = 'message.' + message.id; - iframe = $( - '' - ).bind('load', function () { - $.each(names, function (i, name) { - message[name] = options[name]; - }); - message.dataType = message.dataType.replace('postmessage ', ''); - $(window).bind(eventName, function (e) { - e = e.originalEvent; - var data = e.data, - ev; - if (e.origin === target && data.id === message.id) { - if (data.type === 'progress') { - ev = document.createEvent('Event'); - ev.initEvent(data.type, false, true); - $.extend(ev, data); - xhrUpload.dispatchEvent(ev); - } else { - completeCallback( - data.status, - data.statusText, - {postmessage: data.result}, - data.headers - ); - iframe.remove(); - $(window).unbind(eventName); - } - } - }); - iframe[0].contentWindow.postMessage( - message, - target - ); - }).appendTo(document.body); - }, - abort: function () { - if (iframe) { - iframe.remove(); - } - } - }; - } - }); - -})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/js/cors/jquery.xdr-transport.js --- a/OrthancExplorer/libs/jquery-file-upload/js/cors/jquery.xdr-transport.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -/* - * jQuery XDomainRequest Transport Plugin 1.1.2 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2011, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - * - * Based on Julian Aubourg's ajaxHooks xdr.js: - * https://github.com/jaubourg/ajaxHooks/ - */ - -/*jslint unparam: true */ -/*global define, window, XDomainRequest */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define(['jquery'], factory); - } else { - // Browser globals: - factory(window.jQuery); - } -}(function ($) { - 'use strict'; - if (window.XDomainRequest && !$.support.cors) { - $.ajaxTransport(function (s) { - if (s.crossDomain && s.async) { - if (s.timeout) { - s.xdrTimeout = s.timeout; - delete s.timeout; - } - var xdr; - return { - send: function (headers, completeCallback) { - function callback(status, statusText, responses, responseHeaders) { - xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; - xdr = null; - completeCallback(status, statusText, responses, responseHeaders); - } - xdr = new XDomainRequest(); - // XDomainRequest only supports GET and POST: - if (s.type === 'DELETE') { - s.url = s.url + (/\?/.test(s.url) ? '&' : '?') + - '_method=DELETE'; - s.type = 'POST'; - } else if (s.type === 'PUT') { - s.url = s.url + (/\?/.test(s.url) ? '&' : '?') + - '_method=PUT'; - s.type = 'POST'; - } - xdr.open(s.type, s.url); - xdr.onload = function () { - callback( - 200, - 'OK', - {text: xdr.responseText}, - 'Content-Type: ' + xdr.contentType - ); - }; - xdr.onerror = function () { - callback(404, 'Not Found'); - }; - if (s.xdrTimeout) { - xdr.ontimeout = function () { - callback(0, 'timeout'); - }; - xdr.timeout = s.xdrTimeout; - } - xdr.send((s.hasContent && s.data) || null); - }, - abort: function () { - if (xdr) { - xdr.onerror = $.noop(); - xdr.abort(); - } - } - }; - } - }); - } -})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload-fp.js --- a/OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload-fp.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,219 +0,0 @@ -/* - * jQuery File Upload File Processing Plugin 1.0 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2012, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/*jslint nomen: true, unparam: true, regexp: true */ -/*global define, window, document */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define([ - 'jquery', - 'load-image', - 'canvas-to-blob', - './jquery.fileupload' - ], factory); - } else { - // Browser globals: - factory( - window.jQuery, - window.loadImage - ); - } -}(function ($, loadImage) { - 'use strict'; - - // The File Upload IP version extends the basic fileupload widget - // with file processing functionality: - $.widget('blueimpFP.fileupload', $.blueimp.fileupload, { - - options: { - // The list of file processing actions: - process: [ - /* - { - action: 'load', - fileTypes: /^image\/(gif|jpeg|png)$/, - maxFileSize: 20000000 // 20MB - }, - { - action: 'resize', - maxWidth: 1920, - maxHeight: 1200, - minWidth: 800, - minHeight: 600 - }, - { - action: 'save' - } - */ - ], - - // The add callback is invoked as soon as files are added to the - // fileupload widget (via file input selection, drag & drop or add - // API call). See the basic file upload widget for more information: - add: function (e, data) { - $(this).fileupload('process', data).done(function () { - data.submit(); - }); - } - }, - - processActions: { - // Loads the image given via data.files and data.index - // as canvas element. - // Accepts the options fileTypes (regular expression) - // and maxFileSize (integer) to limit the files to load: - load: function (data, options) { - var that = this, - file = data.files[data.index], - dfd = $.Deferred(); - if (window.HTMLCanvasElement && - window.HTMLCanvasElement.prototype.toBlob && - ($.type(options.maxFileSize) !== 'number' || - file.size < options.maxFileSize) && - (!options.fileTypes || - options.fileTypes.test(file.type))) { - loadImage( - file, - function (canvas) { - data.canvas = canvas; - dfd.resolveWith(that, [data]); - }, - {canvas: true} - ); - } else { - dfd.rejectWith(that, [data]); - } - return dfd.promise(); - }, - // Resizes the image given as data.canvas and updates - // data.canvas with the resized image. - // Accepts the options maxWidth, maxHeight, minWidth and - // minHeight to scale the given image: - resize: function (data, options) { - if (data.canvas) { - var canvas = loadImage.scale(data.canvas, options); - if (canvas.width !== data.canvas.width || - canvas.height !== data.canvas.height) { - data.canvas = canvas; - data.processed = true; - } - } - return data; - }, - // Saves the processed image given as data.canvas - // inplace at data.index of data.files: - save: function (data, options) { - // Do nothing if no processing has happened: - if (!data.canvas || !data.processed) { - return data; - } - var that = this, - file = data.files[data.index], - name = file.name, - dfd = $.Deferred(), - callback = function (blob) { - if (!blob.name) { - if (file.type === blob.type) { - blob.name = file.name; - } else if (file.name) { - blob.name = file.name.replace( - /\..+$/, - '.' + blob.type.substr(6) - ); - } - } - // Store the created blob at the position - // of the original file in the files list: - data.files[data.index] = blob; - dfd.resolveWith(that, [data]); - }; - // Use canvas.mozGetAsFile directly, to retain the filename, as - // Gecko doesn't support the filename option for FormData.append: - if (data.canvas.mozGetAsFile) { - callback(data.canvas.mozGetAsFile( - (/^image\/(jpeg|png)$/.test(file.type) && name) || - ((name && name.replace(/\..+$/, '')) || - 'blob') + '.png', - file.type - )); - } else { - data.canvas.toBlob(callback, file.type); - } - return dfd.promise(); - } - }, - - // Resizes the file at the given index and stores the created blob at - // the original position of the files list, returns a Promise object: - _processFile: function (files, index, options) { - var that = this, - dfd = $.Deferred().resolveWith(that, [{ - files: files, - index: index - }]), - chain = dfd.promise(); - that._processing += 1; - $.each(options.process, function (i, settings) { - chain = chain.pipe(function (data) { - return that.processActions[settings.action] - .call(this, data, settings); - }); - }); - chain.always(function () { - that._processing -= 1; - if (that._processing === 0) { - that.element - .removeClass('fileupload-processing'); - } - }); - if (that._processing === 1) { - that.element.addClass('fileupload-processing'); - } - return chain; - }, - - // Processes the files given as files property of the data parameter, - // returns a Promise object that allows one to bind a done handler, which - // will be invoked after processing all files (inplace) is done: - process: function (data) { - var that = this, - options = $.extend({}, this.options, data); - if (options.process && options.process.length && - this._isXHRUpload(options)) { - $.each(data.files, function (index, file) { - that._processingQueue = that._processingQueue.pipe( - function () { - var dfd = $.Deferred(); - that._processFile(data.files, index, options) - .always(function () { - dfd.resolveWith(that); - }); - return dfd.promise(); - } - ); - }); - } - return this._processingQueue; - }, - - _create: function () { - $.blueimp.fileupload.prototype._create.call(this); - this._processing = 0; - this._processingQueue = $.Deferred().resolveWith(this) - .promise(); - } - - }); - -})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload-ui.js --- a/OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload-ui.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,702 +0,0 @@ -/* - * jQuery File Upload User Interface Plugin 6.9.1 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2010, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/*jslint nomen: true, unparam: true, regexp: true */ -/*global define, window, document, URL, webkitURL, FileReader */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define([ - 'jquery', - 'tmpl', - 'load-image', - './jquery.fileupload-fp' - ], factory); - } else { - // Browser globals: - factory( - window.jQuery, - window.tmpl, - window.loadImage - ); - } -}(function ($, tmpl, loadImage) { - 'use strict'; - - // The UI version extends the FP (file processing) version or the basic - // file upload widget and adds complete user interface interaction: - var parentWidget = ($.blueimpFP || $.blueimp).fileupload; - $.widget('blueimpUI.fileupload', parentWidget, { - - options: { - // By default, files added to the widget are uploaded as soon - // as the user clicks on the start buttons. To enable automatic - // uploads, set the following option to true: - autoUpload: false, - // The following option limits the number of files that are - // allowed to be uploaded using this widget: - maxNumberOfFiles: undefined, - // The maximum allowed file size: - maxFileSize: undefined, - // The minimum allowed file size: - minFileSize: undefined, - // The regular expression for allowed file types, matches - // against either file type or file name: - acceptFileTypes: /.+$/i, - // The regular expression to define for which files a preview - // image is shown, matched against the file type: - previewSourceFileTypes: /^image\/(gif|jpeg|png)$/, - // The maximum file size of images that are to be displayed as preview: - previewSourceMaxFileSize: 5000000, // 5MB - // The maximum width of the preview images: - previewMaxWidth: 80, - // The maximum height of the preview images: - previewMaxHeight: 80, - // By default, preview images are displayed as canvas elements - // if supported by the browser. Set the following option to false - // to always display preview images as img elements: - previewAsCanvas: true, - // The ID of the upload template: - uploadTemplateId: 'template-upload', - // The ID of the download template: - downloadTemplateId: 'template-download', - // The container for the list of files. If undefined, it is set to - // an element with class "files" inside of the widget element: - filesContainer: undefined, - // By default, files are appended to the files container. - // Set the following option to true, to prepend files instead: - prependFiles: false, - // The expected data type of the upload response, sets the dataType - // option of the $.ajax upload requests: - dataType: 'json', - - // The add callback is invoked as soon as files are added to the fileupload - // widget (via file input selection, drag & drop or add API call). - // See the basic file upload widget for more information: - add: function (e, data) { - var that = $(this).data('fileupload'), - options = that.options, - files = data.files; - $(this).fileupload('process', data).done(function () { - that._adjustMaxNumberOfFiles(-files.length); - data.isAdjusted = true; - data.files.valid = data.isValidated = that._validate(files); - data.context = that._renderUpload(files).data('data', data); - options.filesContainer[ - options.prependFiles ? 'prepend' : 'append' - ](data.context); - that._renderPreviews(files, data.context); - that._forceReflow(data.context); - that._transition(data.context).done( - function () { - if ((that._trigger('added', e, data) !== false) && - (options.autoUpload || data.autoUpload) && - data.autoUpload !== false && data.isValidated) { - data.submit(); - } - } - ); - }); - }, - // Callback for the start of each file upload request: - send: function (e, data) { - var that = $(this).data('fileupload'); - if (!data.isValidated) { - if (!data.isAdjusted) { - that._adjustMaxNumberOfFiles(-data.files.length); - } - if (!that._validate(data.files)) { - return false; - } - } - if (data.context && data.dataType && - data.dataType.substr(0, 6) === 'iframe') { - // Iframe Transport does not support progress events. - // In lack of an indeterminate progress bar, we set - // the progress to 100%, showing the full animated bar: - data.context - .find('.progress').addClass( - !$.support.transition && 'progress-animated' - ) - .attr('aria-valuenow', 100) - .find('.bar').css( - 'width', - '100%' - ); - } - return that._trigger('sent', e, data); - }, - // Callback for successful uploads: - done: function (e, data) { - var that = $(this).data('fileupload'), - template; - if (data.context) { - data.context.each(function (index) { - var file = ($.isArray(data.result) && - data.result[index]) || {error: 'emptyResult'}; - if (file.error) { - that._adjustMaxNumberOfFiles(1); - } - that._transition($(this)).done( - function () { - var node = $(this); - template = that._renderDownload([file]) - .css('height', node.height()) - .replaceAll(node); - that._forceReflow(template); - that._transition(template).done( - function () { - data.context = $(this); - that._trigger('completed', e, data); - } - ); - } - ); - }); - } else { - template = that._renderDownload(data.result) - .appendTo(that.options.filesContainer); - that._forceReflow(template); - that._transition(template).done( - function () { - data.context = $(this); - that._trigger('completed', e, data); - } - ); - } - }, - // Callback for failed (abort or error) uploads: - fail: function (e, data) { - var that = $(this).data('fileupload'), - template; - that._adjustMaxNumberOfFiles(data.files.length); - if (data.context) { - data.context.each(function (index) { - if (data.errorThrown !== 'abort') { - var file = data.files[index]; - file.error = file.error || data.errorThrown || - true; - that._transition($(this)).done( - function () { - var node = $(this); - template = that._renderDownload([file]) - .replaceAll(node); - that._forceReflow(template); - that._transition(template).done( - function () { - data.context = $(this); - that._trigger('failed', e, data); - } - ); - } - ); - } else { - that._transition($(this)).done( - function () { - $(this).remove(); - that._trigger('failed', e, data); - } - ); - } - }); - } else if (data.errorThrown !== 'abort') { - that._adjustMaxNumberOfFiles(-data.files.length); - data.context = that._renderUpload(data.files) - .appendTo(that.options.filesContainer) - .data('data', data); - that._forceReflow(data.context); - that._transition(data.context).done( - function () { - data.context = $(this); - that._trigger('failed', e, data); - } - ); - } else { - that._trigger('failed', e, data); - } - }, - // Callback for upload progress events: - progress: function (e, data) { - if (data.context) { - var progress = parseInt(data.loaded / data.total * 100, 10); - data.context.find('.progress') - .attr('aria-valuenow', progress) - .find('.bar').css( - 'width', - progress + '%' - ); - } - }, - // Callback for global upload progress events: - progressall: function (e, data) { - var $this = $(this), - progress = parseInt(data.loaded / data.total * 100, 10), - globalProgressNode = $this.find('.fileupload-progress'), - extendedProgressNode = globalProgressNode - .find('.progress-extended'); - if (extendedProgressNode.length) { - extendedProgressNode.html( - $this.data('fileupload')._renderExtendedProgress(data) - ); - } - globalProgressNode - .find('.progress') - .attr('aria-valuenow', progress) - .find('.bar').css( - 'width', - progress + '%' - ); - }, - // Callback for uploads start, equivalent to the global ajaxStart event: - start: function (e) { - var that = $(this).data('fileupload'); - that._transition($(this).find('.fileupload-progress')).done( - function () { - that._trigger('started', e); - } - ); - }, - // Callback for uploads stop, equivalent to the global ajaxStop event: - stop: function (e) { - var that = $(this).data('fileupload'); - that._transition($(this).find('.fileupload-progress')).done( - function () { - $(this).find('.progress') - .attr('aria-valuenow', '0') - .find('.bar').css('width', '0%'); - $(this).find('.progress-extended').html(' '); - that._trigger('stopped', e); - } - ); - }, - // Callback for file deletion: - destroy: function (e, data) { - var that = $(this).data('fileupload'); - if (data.url) { - $.ajax(data); - that._adjustMaxNumberOfFiles(1); - } - that._transition(data.context).done( - function () { - $(this).remove(); - that._trigger('destroyed', e, data); - } - ); - } - }, - - // Link handler, that allows one to download files - // by drag & drop of the links to the desktop: - _enableDragToDesktop: function () { - var link = $(this), - url = link.prop('href'), - name = link.prop('download'), - type = 'application/octet-stream'; - link.bind('dragstart', function (e) { - try { - e.originalEvent.dataTransfer.setData( - 'DownloadURL', - [type, name, url].join(':') - ); - } catch (err) {} - }); - }, - - _adjustMaxNumberOfFiles: function (operand) { - if (typeof this.options.maxNumberOfFiles === 'number') { - this.options.maxNumberOfFiles += operand; - if (this.options.maxNumberOfFiles < 1) { - this._disableFileInputButton(); - } else { - this._enableFileInputButton(); - } - } - }, - - _formatFileSize: function (bytes) { - if (typeof bytes !== 'number') { - return ''; - } - if (bytes >= 1000000000) { - return (bytes / 1000000000).toFixed(2) + ' GB'; - } - if (bytes >= 1000000) { - return (bytes / 1000000).toFixed(2) + ' MB'; - } - return (bytes / 1000).toFixed(2) + ' KB'; - }, - - _formatBitrate: function (bits) { - if (typeof bits !== 'number') { - return ''; - } - if (bits >= 1000000000) { - return (bits / 1000000000).toFixed(2) + ' Gbit/s'; - } - if (bits >= 1000000) { - return (bits / 1000000).toFixed(2) + ' Mbit/s'; - } - if (bits >= 1000) { - return (bits / 1000).toFixed(2) + ' kbit/s'; - } - return bits + ' bit/s'; - }, - - _formatTime: function (seconds) { - var date = new Date(seconds * 1000), - days = parseInt(seconds / 86400, 10); - days = days ? days + 'd ' : ''; - return days + - ('0' + date.getUTCHours()).slice(-2) + ':' + - ('0' + date.getUTCMinutes()).slice(-2) + ':' + - ('0' + date.getUTCSeconds()).slice(-2); - }, - - _formatPercentage: function (floatValue) { - return (floatValue * 100).toFixed(2) + ' %'; - }, - - _renderExtendedProgress: function (data) { - return this._formatBitrate(data.bitrate) + ' | ' + - this._formatTime( - (data.total - data.loaded) * 8 / data.bitrate - ) + ' | ' + - this._formatPercentage( - data.loaded / data.total - ) + ' | ' + - this._formatFileSize(data.loaded) + ' / ' + - this._formatFileSize(data.total); - }, - - _hasError: function (file) { - if (file.error) { - return file.error; - } - // The number of added files is subtracted from - // maxNumberOfFiles before validation, so we check if - // maxNumberOfFiles is below 0 (instead of below 1): - if (this.options.maxNumberOfFiles < 0) { - return 'maxNumberOfFiles'; - } - // Files are accepted if either the file type or the file name - // matches against the acceptFileTypes regular expression, as - // only browsers with support for the File API report the type: - if (!(this.options.acceptFileTypes.test(file.type) || - this.options.acceptFileTypes.test(file.name))) { - return 'acceptFileTypes'; - } - if (this.options.maxFileSize && - file.size > this.options.maxFileSize) { - return 'maxFileSize'; - } - if (typeof file.size === 'number' && - file.size < this.options.minFileSize) { - return 'minFileSize'; - } - return null; - }, - - _validate: function (files) { - var that = this, - valid = !!files.length; - $.each(files, function (index, file) { - file.error = that._hasError(file); - if (file.error) { - valid = false; - } - }); - return valid; - }, - - _renderTemplate: function (func, files) { - if (!func) { - return $(); - } - var result = func({ - files: files, - formatFileSize: this._formatFileSize, - options: this.options - }); - if (result instanceof $) { - return result; - } - return $(this.options.templatesContainer).html(result).children(); - }, - - _renderPreview: function (file, node) { - var that = this, - options = this.options, - dfd = $.Deferred(); - return ((loadImage && loadImage( - file, - function (img) { - node.append(img); - that._forceReflow(node); - that._transition(node).done(function () { - dfd.resolveWith(node); - }); - if (!$.contains(document.body, node[0])) { - // If the element is not part of the DOM, - // transition events are not triggered, - // so we have to resolve manually: - dfd.resolveWith(node); - } - }, - { - maxWidth: options.previewMaxWidth, - maxHeight: options.previewMaxHeight, - canvas: options.previewAsCanvas - } - )) || dfd.resolveWith(node)) && dfd; - }, - - _renderPreviews: function (files, nodes) { - var that = this, - options = this.options; - nodes.find('.preview span').each(function (index, element) { - var file = files[index]; - if (options.previewSourceFileTypes.test(file.type) && - ($.type(options.previewSourceMaxFileSize) !== 'number' || - file.size < options.previewSourceMaxFileSize)) { - that._processingQueue = that._processingQueue.pipe(function () { - var dfd = $.Deferred(); - that._renderPreview(file, $(element)).done( - function () { - dfd.resolveWith(that); - } - ); - return dfd.promise(); - }); - } - }); - return this._processingQueue; - }, - - _renderUpload: function (files) { - return this._renderTemplate( - this.options.uploadTemplate, - files - ); - }, - - _renderDownload: function (files) { - return this._renderTemplate( - this.options.downloadTemplate, - files - ).find('a[download]').each(this._enableDragToDesktop).end(); - }, - - _startHandler: function (e) { - e.preventDefault(); - var button = $(this), - template = button.closest('.template-upload'), - data = template.data('data'); - if (data && data.submit && !data.jqXHR && data.submit()) { - button.prop('disabled', true); - } - }, - - _cancelHandler: function (e) { - e.preventDefault(); - var template = $(this).closest('.template-upload'), - data = template.data('data') || {}; - if (!data.jqXHR) { - data.errorThrown = 'abort'; - e.data.fileupload._trigger('fail', e, data); - } else { - data.jqXHR.abort(); - } - }, - - _deleteHandler: function (e) { - e.preventDefault(); - var button = $(this); - e.data.fileupload._trigger('destroy', e, { - context: button.closest('.template-download'), - url: button.attr('data-url'), - type: button.attr('data-type') || 'DELETE', - dataType: e.data.fileupload.options.dataType - }); - }, - - _forceReflow: function (node) { - return $.support.transition && node.length && - node[0].offsetWidth; - }, - - _transition: function (node) { - var dfd = $.Deferred(); - if ($.support.transition && node.hasClass('fade')) { - node.bind( - $.support.transition.end, - function (e) { - // Make sure we don't respond to other transitions events - // in the container element, e.g. from button elements: - if (e.target === node[0]) { - node.unbind($.support.transition.end); - dfd.resolveWith(node); - } - } - ).toggleClass('in'); - } else { - node.toggleClass('in'); - dfd.resolveWith(node); - } - return dfd; - }, - - _initButtonBarEventHandlers: function () { - var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), - filesList = this.options.filesContainer, - ns = this.options.namespace; - fileUploadButtonBar.find('.start') - .bind('click.' + ns, function (e) { - e.preventDefault(); - filesList.find('.start button').click(); - }); - fileUploadButtonBar.find('.cancel') - .bind('click.' + ns, function (e) { - e.preventDefault(); - filesList.find('.cancel button').click(); - }); - fileUploadButtonBar.find('.delete') - .bind('click.' + ns, function (e) { - e.preventDefault(); - filesList.find('.delete input:checked') - .siblings('button').click(); - fileUploadButtonBar.find('.toggle') - .prop('checked', false); - }); - fileUploadButtonBar.find('.toggle') - .bind('change.' + ns, function (e) { - filesList.find('.delete input').prop( - 'checked', - $(this).is(':checked') - ); - }); - }, - - _destroyButtonBarEventHandlers: function () { - this.element.find('.fileupload-buttonbar button') - .unbind('click.' + this.options.namespace); - this.element.find('.fileupload-buttonbar .toggle') - .unbind('change.' + this.options.namespace); - }, - - _initEventHandlers: function () { - parentWidget.prototype._initEventHandlers.call(this); - var eventData = {fileupload: this}; - this.options.filesContainer - .delegate( - '.start button', - 'click.' + this.options.namespace, - eventData, - this._startHandler - ) - .delegate( - '.cancel button', - 'click.' + this.options.namespace, - eventData, - this._cancelHandler - ) - .delegate( - '.delete button', - 'click.' + this.options.namespace, - eventData, - this._deleteHandler - ); - this._initButtonBarEventHandlers(); - }, - - _destroyEventHandlers: function () { - var options = this.options; - this._destroyButtonBarEventHandlers(); - options.filesContainer - .undelegate('.start button', 'click.' + options.namespace) - .undelegate('.cancel button', 'click.' + options.namespace) - .undelegate('.delete button', 'click.' + options.namespace); - parentWidget.prototype._destroyEventHandlers.call(this); - }, - - _enableFileInputButton: function () { - this.element.find('.fileinput-button input') - .prop('disabled', false) - .parent().removeClass('disabled'); - }, - - _disableFileInputButton: function () { - this.element.find('.fileinput-button input') - .prop('disabled', true) - .parent().addClass('disabled'); - }, - - _initTemplates: function () { - var options = this.options; - options.templatesContainer = document.createElement( - options.filesContainer.prop('nodeName') - ); - if (tmpl) { - if (options.uploadTemplateId) { - options.uploadTemplate = tmpl(options.uploadTemplateId); - } - if (options.downloadTemplateId) { - options.downloadTemplate = tmpl(options.downloadTemplateId); - } - } - }, - - _initFilesContainer: function () { - var options = this.options; - if (options.filesContainer === undefined) { - options.filesContainer = this.element.find('.files'); - } else if (!(options.filesContainer instanceof $)) { - options.filesContainer = $(options.filesContainer); - } - }, - - _initSpecialOptions: function () { - parentWidget.prototype._initSpecialOptions.call(this); - this._initFilesContainer(); - this._initTemplates(); - }, - - _create: function () { - parentWidget.prototype._create.call(this); - this._refreshOptionsList.push( - 'filesContainer', - 'uploadTemplateId', - 'downloadTemplateId' - ); - if (!$.blueimpFP) { - this._processingQueue = $.Deferred().resolveWith(this).promise(); - this.process = function () { - return this._processingQueue; - }; - } - }, - - enable: function () { - parentWidget.prototype.enable.call(this); - this.element.find('input, button').prop('disabled', false); - this._enableFileInputButton(); - }, - - disable: function () { - this.element.find('input, button').prop('disabled', true); - this._disableFileInputButton(); - parentWidget.prototype.disable.call(this); - } - - }); - -})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload.js --- a/OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,968 +0,0 @@ -/* - * jQuery File Upload Plugin 5.12 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2010, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/*jslint nomen: true, unparam: true, regexp: true */ -/*global define, window, document, Blob, FormData, location */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define([ - 'jquery', - 'jquery.ui.widget' - ], factory); - } else { - // Browser globals: - factory(window.jQuery); - } -}(function ($) { - 'use strict'; - - // The FileReader API is not actually used, but works as feature detection, - // as e.g. Safari supports XHR file uploads via the FormData API, - // but not non-multipart XHR file uploads: - $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader); - $.support.xhrFormDataFileUpload = !!window.FormData; - - // The fileupload widget listens for change events on file input fields defined - // via fileInput setting and paste or drop events of the given dropZone. - // In addition to the default jQuery Widget methods, the fileupload widget - // exposes the "add" and "send" methods, to add or directly send files using - // the fileupload API. - // By default, files added via file input selection, paste, drag & drop or - // "add" method are uploaded immediately, but it is possible to override - // the "add" callback option to queue file uploads. - $.widget('blueimp.fileupload', { - - options: { - // The namespace used for event handler binding on the dropZone and - // fileInput collections. - // If not set, the name of the widget ("fileupload") is used. - namespace: undefined, - // The drop target collection, by the default the complete document. - // Set to null or an empty collection to disable drag & drop support: - dropZone: $(document), - // The file input field collection, that is listened for change events. - // If undefined, it is set to the file input fields inside - // of the widget element on plugin initialization. - // Set to null or an empty collection to disable the change listener. - fileInput: undefined, - // By default, the file input field is replaced with a clone after - // each input field change event. This is required for iframe transport - // queues and allows change events to be fired for the same file - // selection, but can be disabled by setting the following option to false: - replaceFileInput: true, - // The parameter name for the file form data (the request argument name). - // If undefined or empty, the name property of the file input field is - // used, or "files[]" if the file input name property is also empty, - // can be a string or an array of strings: - paramName: undefined, - // By default, each file of a selection is uploaded using an individual - // request for XHR type uploads. Set to false to upload file - // selections in one request each: - singleFileUploads: true, - // To limit the number of files uploaded with one XHR request, - // set the following option to an integer greater than 0: - limitMultiFileUploads: undefined, - // Set the following option to true to issue all file upload requests - // in a sequential order: - sequentialUploads: false, - // To limit the number of concurrent uploads, - // set the following option to an integer greater than 0: - limitConcurrentUploads: undefined, - // Set the following option to true to force iframe transport uploads: - forceIframeTransport: false, - // Set the following option to the location of a redirect url on the - // origin server, for cross-domain iframe transport uploads: - redirect: undefined, - // The parameter name for the redirect url, sent as part of the form - // data and set to 'redirect' if this option is empty: - redirectParamName: undefined, - // Set the following option to the location of a postMessage window, - // to enable postMessage transport uploads: - postMessage: undefined, - // By default, XHR file uploads are sent as multipart/form-data. - // The iframe transport is always using multipart/form-data. - // Set to false to enable non-multipart XHR uploads: - multipart: true, - // To upload large files in smaller chunks, set the following option - // to a preferred maximum chunk size. If set to 0, null or undefined, - // or the browser does not support the required Blob API, files will - // be uploaded as a whole. - maxChunkSize: undefined, - // When a non-multipart upload or a chunked multipart upload has been - // aborted, this option can be used to resume the upload by setting - // it to the size of the already uploaded bytes. This option is most - // useful when modifying the options object inside of the "add" or - // "send" callbacks, as the options are cloned for each file upload. - uploadedBytes: undefined, - // By default, failed (abort or error) file uploads are removed from the - // global progress calculation. Set the following option to false to - // prevent recalculating the global progress data: - recalculateProgress: true, - // Interval in milliseconds to calculate and trigger progress events: - progressInterval: 100, - // Interval in milliseconds to calculate progress bitrate: - bitrateInterval: 500, - - // Additional form data to be sent along with the file uploads can be set - // using this option, which accepts an array of objects with name and - // value properties, a function returning such an array, a FormData - // object (for XHR file uploads), or a simple object. - // The form of the first fileInput is given as parameter to the function: - formData: function (form) { - return form.serializeArray(); - }, - - // The add callback is invoked as soon as files are added to the fileupload - // widget (via file input selection, drag & drop, paste or add API call). - // If the singleFileUploads option is enabled, this callback will be - // called once for each file in the selection for XHR file uploads, else - // once for each file selection. - // The upload starts when the submit method is invoked on the data parameter. - // The data object contains a files property holding the added files - // and allows one to override plugin options as well as define ajax settings. - // Listeners for this callback can also be bound the following way: - // .bind('fileuploadadd', func); - // data.submit() returns a Promise object and allows one to attach additional - // handlers using jQuery's Deferred callbacks: - // data.submit().done(func).fail(func).always(func); - add: function (e, data) { - data.submit(); - }, - - // Other callbacks: - // Callback for the submit event of each file upload: - // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); - // Callback for the start of each file upload request: - // send: function (e, data) {}, // .bind('fileuploadsend', func); - // Callback for successful uploads: - // done: function (e, data) {}, // .bind('fileuploaddone', func); - // Callback for failed (abort or error) uploads: - // fail: function (e, data) {}, // .bind('fileuploadfail', func); - // Callback for completed (success, abort or error) requests: - // always: function (e, data) {}, // .bind('fileuploadalways', func); - // Callback for upload progress events: - // progress: function (e, data) {}, // .bind('fileuploadprogress', func); - // Callback for global upload progress events: - // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); - // Callback for uploads start, equivalent to the global ajaxStart event: - // start: function (e) {}, // .bind('fileuploadstart', func); - // Callback for uploads stop, equivalent to the global ajaxStop event: - // stop: function (e) {}, // .bind('fileuploadstop', func); - // Callback for change events of the fileInput collection: - // change: function (e, data) {}, // .bind('fileuploadchange', func); - // Callback for paste events to the dropZone collection: - // paste: function (e, data) {}, // .bind('fileuploadpaste', func); - // Callback for drop events of the dropZone collection: - // drop: function (e, data) {}, // .bind('fileuploaddrop', func); - // Callback for dragover events of the dropZone collection: - // dragover: function (e) {}, // .bind('fileuploaddragover', func); - - // The plugin options are used as settings object for the ajax calls. - // The following are jQuery ajax settings required for the file uploads: - processData: false, - contentType: false, - cache: false - }, - - // A list of options that require a refresh after assigning a new value: - _refreshOptionsList: [ - 'namespace', - 'dropZone', - 'fileInput', - 'multipart', - 'forceIframeTransport' - ], - - _BitrateTimer: function () { - this.timestamp = +(new Date()); - this.loaded = 0; - this.bitrate = 0; - this.getBitrate = function (now, loaded, interval) { - var timeDiff = now - this.timestamp; - if (!this.bitrate || !interval || timeDiff > interval) { - this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; - this.loaded = loaded; - this.timestamp = now; - } - return this.bitrate; - }; - }, - - _isXHRUpload: function (options) { - return !options.forceIframeTransport && - ((!options.multipart && $.support.xhrFileUpload) || - $.support.xhrFormDataFileUpload); - }, - - _getFormData: function (options) { - var formData; - if (typeof options.formData === 'function') { - return options.formData(options.form); - } - if ($.isArray(options.formData)) { - return options.formData; - } - if (options.formData) { - formData = []; - $.each(options.formData, function (name, value) { - formData.push({name: name, value: value}); - }); - return formData; - } - return []; - }, - - _getTotal: function (files) { - var total = 0; - $.each(files, function (index, file) { - total += file.size || 1; - }); - return total; - }, - - _onProgress: function (e, data) { - if (e.lengthComputable) { - var now = +(new Date()), - total, - loaded; - if (data._time && data.progressInterval && - (now - data._time < data.progressInterval) && - e.loaded !== e.total) { - return; - } - data._time = now; - total = data.total || this._getTotal(data.files); - loaded = parseInt( - e.loaded / e.total * (data.chunkSize || total), - 10 - ) + (data.uploadedBytes || 0); - this._loaded += loaded - (data.loaded || data.uploadedBytes || 0); - data.lengthComputable = true; - data.loaded = loaded; - data.total = total; - data.bitrate = data._bitrateTimer.getBitrate( - now, - loaded, - data.bitrateInterval - ); - // Trigger a custom progress event with a total data property set - // to the file size(s) of the current upload and a loaded data - // property calculated accordingly: - this._trigger('progress', e, data); - // Trigger a global progress event for all current file uploads, - // including ajax calls queued for sequential file uploads: - this._trigger('progressall', e, { - lengthComputable: true, - loaded: this._loaded, - total: this._total, - bitrate: this._bitrateTimer.getBitrate( - now, - this._loaded, - data.bitrateInterval - ) - }); - } - }, - - _initProgressListener: function (options) { - var that = this, - xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); - // Accesss to the native XHR object is required to add event listeners - // for the upload progress event: - if (xhr.upload) { - $(xhr.upload).bind('progress', function (e) { - var oe = e.originalEvent; - // Make sure the progress event properties get copied over: - e.lengthComputable = oe.lengthComputable; - e.loaded = oe.loaded; - e.total = oe.total; - that._onProgress(e, options); - }); - options.xhr = function () { - return xhr; - }; - } - }, - - _initXHRData: function (options) { - var formData, - file = options.files[0], - // Ignore non-multipart setting if not supported: - multipart = options.multipart || !$.support.xhrFileUpload, - paramName = options.paramName[0]; - if (!multipart || options.blob) { - // For non-multipart uploads and chunked uploads, - // file meta data is not part of the request body, - // so we transmit this data as part of the HTTP headers. - // For cross domain requests, these headers must be allowed - // via Access-Control-Allow-Headers or removed using - // the beforeSend callback: - options.headers = $.extend(options.headers, { - 'X-File-Name': file.name, - 'X-File-Type': file.type, - 'X-File-Size': file.size - }); - if (!options.blob) { - // Non-chunked non-multipart upload: - options.contentType = file.type; - options.data = file; - } else if (!multipart) { - // Chunked non-multipart upload: - options.contentType = 'application/octet-stream'; - options.data = options.blob; - } - } - if (multipart && $.support.xhrFormDataFileUpload) { - if (options.postMessage) { - // window.postMessage does not allow sending FormData - // objects, so we just add the File/Blob objects to - // the formData array and let the postMessage window - // create the FormData object out of this array: - formData = this._getFormData(options); - if (options.blob) { - formData.push({ - name: paramName, - value: options.blob - }); - } else { - $.each(options.files, function (index, file) { - formData.push({ - name: options.paramName[index] || paramName, - value: file - }); - }); - } - } else { - if (options.formData instanceof FormData) { - formData = options.formData; - } else { - formData = new FormData(); - $.each(this._getFormData(options), function (index, field) { - formData.append(field.name, field.value); - }); - } - if (options.blob) { - formData.append(paramName, options.blob, file.name); - } else { - $.each(options.files, function (index, file) { - // File objects are also Blob instances. - // This check allows the tests to run with - // dummy objects: - if (file instanceof Blob) { - formData.append( - options.paramName[index] || paramName, - file, - file.name - ); - } - }); - } - } - options.data = formData; - } - // Blob reference is not needed anymore, free memory: - options.blob = null; - }, - - _initIframeSettings: function (options) { - // Setting the dataType to iframe enables the iframe transport: - options.dataType = 'iframe ' + (options.dataType || ''); - // The iframe transport accepts a serialized array as form data: - options.formData = this._getFormData(options); - // Add redirect url to form data on cross-domain uploads: - if (options.redirect && $('').prop('href', options.url) - .prop('host') !== location.host) { - options.formData.push({ - name: options.redirectParamName || 'redirect', - value: options.redirect - }); - } - }, - - _initDataSettings: function (options) { - if (this._isXHRUpload(options)) { - if (!this._chunkedUpload(options, true)) { - if (!options.data) { - this._initXHRData(options); - } - this._initProgressListener(options); - } - if (options.postMessage) { - // Setting the dataType to postmessage enables the - // postMessage transport: - options.dataType = 'postmessage ' + (options.dataType || ''); - } - } else { - this._initIframeSettings(options, 'iframe'); - } - }, - - _getParamName: function (options) { - var fileInput = $(options.fileInput), - paramName = options.paramName; - if (!paramName) { - paramName = []; - fileInput.each(function () { - var input = $(this), - name = input.prop('name') || 'files[]', - i = (input.prop('files') || [1]).length; - while (i) { - paramName.push(name); - i -= 1; - } - }); - if (!paramName.length) { - paramName = [fileInput.prop('name') || 'files[]']; - } - } else if (!$.isArray(paramName)) { - paramName = [paramName]; - } - return paramName; - }, - - _initFormSettings: function (options) { - // Retrieve missing options from the input field and the - // associated form, if available: - if (!options.form || !options.form.length) { - options.form = $(options.fileInput.prop('form')); - } - options.paramName = this._getParamName(options); - if (!options.url) { - options.url = options.form.prop('action') || location.href; - } - // The HTTP request method must be "POST" or "PUT": - options.type = (options.type || options.form.prop('method') || '') - .toUpperCase(); - if (options.type !== 'POST' && options.type !== 'PUT') { - options.type = 'POST'; - } - }, - - _getAJAXSettings: function (data) { - var options = $.extend({}, this.options, data); - this._initFormSettings(options); - this._initDataSettings(options); - return options; - }, - - // Maps jqXHR callbacks to the equivalent - // methods of the given Promise object: - _enhancePromise: function (promise) { - promise.success = promise.done; - promise.error = promise.fail; - promise.complete = promise.always; - return promise; - }, - - // Creates and returns a Promise object enhanced with - // the jqXHR methods abort, success, error and complete: - _getXHRPromise: function (resolveOrReject, context, args) { - var dfd = $.Deferred(), - promise = dfd.promise(); - context = context || this.options.context || promise; - if (resolveOrReject === true) { - dfd.resolveWith(context, args); - } else if (resolveOrReject === false) { - dfd.rejectWith(context, args); - } - promise.abort = dfd.promise; - return this._enhancePromise(promise); - }, - - // Uploads a file in multiple, sequential requests - // by splitting the file up in multiple blob chunks. - // If the second parameter is true, only tests if the file - // should be uploaded in chunks, but does not invoke any - // upload requests: - _chunkedUpload: function (options, testOnly) { - var that = this, - file = options.files[0], - fs = file.size, - ub = options.uploadedBytes = options.uploadedBytes || 0, - mcs = options.maxChunkSize || fs, - // Use the Blob methods with the slice implementation - // according to the W3C Blob API specification: - slice = file.webkitSlice || file.mozSlice || file.slice, - upload, - n, - jqXHR, - pipe; - if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || - options.data) { - return false; - } - if (testOnly) { - return true; - } - if (ub >= fs) { - file.error = 'uploadedBytes'; - return this._getXHRPromise( - false, - options.context, - [null, 'error', file.error] - ); - } - // n is the number of blobs to upload, - // calculated via filesize, uploaded bytes and max chunk size: - n = Math.ceil((fs - ub) / mcs); - // The chunk upload method accepting the chunk number as parameter: - upload = function (i) { - if (!i) { - return that._getXHRPromise(true, options.context); - } - // Upload the blobs in sequential order: - return upload(i -= 1).pipe(function () { - // Clone the options object for each chunk upload: - var o = $.extend({}, options); - o.blob = slice.call( - file, - ub + i * mcs, - ub + (i + 1) * mcs - ); - // Store the current chunk size, as the blob itself - // will be dereferenced after data processing: - o.chunkSize = o.blob.size; - // Process the upload data (the blob and potential form data): - that._initXHRData(o); - // Add progress listeners for this chunk upload: - that._initProgressListener(o); - jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context)) - .done(function () { - // Create a progress event if upload is done and - // no progress event has been invoked for this chunk: - if (!o.loaded) { - that._onProgress($.Event('progress', { - lengthComputable: true, - loaded: o.chunkSize, - total: o.chunkSize - }), o); - } - options.uploadedBytes = o.uploadedBytes += - o.chunkSize; - }); - return jqXHR; - }); - }; - // Return the piped Promise object, enhanced with an abort method, - // which is delegated to the jqXHR object of the current upload, - // and jqXHR callbacks mapped to the equivalent Promise methods: - pipe = upload(n); - pipe.abort = function () { - return jqXHR.abort(); - }; - return this._enhancePromise(pipe); - }, - - _beforeSend: function (e, data) { - if (this._active === 0) { - // the start callback is triggered when an upload starts - // and no other uploads are currently running, - // equivalent to the global ajaxStart event: - this._trigger('start'); - // Set timer for global bitrate progress calculation: - this._bitrateTimer = new this._BitrateTimer(); - } - this._active += 1; - // Initialize the global progress values: - this._loaded += data.uploadedBytes || 0; - this._total += this._getTotal(data.files); - }, - - _onDone: function (result, textStatus, jqXHR, options) { - if (!this._isXHRUpload(options)) { - // Create a progress event for each iframe load: - this._onProgress($.Event('progress', { - lengthComputable: true, - loaded: 1, - total: 1 - }), options); - } - options.result = result; - options.textStatus = textStatus; - options.jqXHR = jqXHR; - this._trigger('done', null, options); - }, - - _onFail: function (jqXHR, textStatus, errorThrown, options) { - options.jqXHR = jqXHR; - options.textStatus = textStatus; - options.errorThrown = errorThrown; - this._trigger('fail', null, options); - if (options.recalculateProgress) { - // Remove the failed (error or abort) file upload from - // the global progress calculation: - this._loaded -= options.loaded || options.uploadedBytes || 0; - this._total -= options.total || this._getTotal(options.files); - } - }, - - _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { - this._active -= 1; - options.textStatus = textStatus; - if (jqXHRorError && jqXHRorError.always) { - options.jqXHR = jqXHRorError; - options.result = jqXHRorResult; - } else { - options.jqXHR = jqXHRorResult; - options.errorThrown = jqXHRorError; - } - this._trigger('always', null, options); - if (this._active === 0) { - // The stop callback is triggered when all uploads have - // been completed, equivalent to the global ajaxStop event: - this._trigger('stop'); - // Reset the global progress values: - this._loaded = this._total = 0; - this._bitrateTimer = null; - } - }, - - _onSend: function (e, data) { - var that = this, - jqXHR, - slot, - pipe, - options = that._getAJAXSettings(data), - send = function (resolve, args) { - that._sending += 1; - // Set timer for bitrate progress calculation: - options._bitrateTimer = new that._BitrateTimer(); - jqXHR = jqXHR || ( - (resolve !== false && - that._trigger('send', e, options) !== false && - (that._chunkedUpload(options) || $.ajax(options))) || - that._getXHRPromise(false, options.context, args) - ).done(function (result, textStatus, jqXHR) { - that._onDone(result, textStatus, jqXHR, options); - }).fail(function (jqXHR, textStatus, errorThrown) { - that._onFail(jqXHR, textStatus, errorThrown, options); - }).always(function (jqXHRorResult, textStatus, jqXHRorError) { - that._sending -= 1; - that._onAlways( - jqXHRorResult, - textStatus, - jqXHRorError, - options - ); - if (options.limitConcurrentUploads && - options.limitConcurrentUploads > that._sending) { - // Start the next queued upload, - // that has not been aborted: - var nextSlot = that._slots.shift(); - while (nextSlot) { - if (!nextSlot.isRejected()) { - nextSlot.resolve(); - break; - } - nextSlot = that._slots.shift(); - } - } - }); - return jqXHR; - }; - this._beforeSend(e, options); - if (this.options.sequentialUploads || - (this.options.limitConcurrentUploads && - this.options.limitConcurrentUploads <= this._sending)) { - if (this.options.limitConcurrentUploads > 1) { - slot = $.Deferred(); - this._slots.push(slot); - pipe = slot.pipe(send); - } else { - pipe = (this._sequence = this._sequence.pipe(send, send)); - } - // Return the piped Promise object, enhanced with an abort method, - // which is delegated to the jqXHR object of the current upload, - // and jqXHR callbacks mapped to the equivalent Promise methods: - pipe.abort = function () { - var args = [undefined, 'abort', 'abort']; - if (!jqXHR) { - if (slot) { - slot.rejectWith(args); - } - return send(false, args); - } - return jqXHR.abort(); - }; - return this._enhancePromise(pipe); - } - return send(); - }, - - _onAdd: function (e, data) { - var that = this, - result = true, - options = $.extend({}, this.options, data), - limit = options.limitMultiFileUploads, - paramName = this._getParamName(options), - paramNameSet, - paramNameSlice, - fileSet, - i; - if (!(options.singleFileUploads || limit) || - !this._isXHRUpload(options)) { - fileSet = [data.files]; - paramNameSet = [paramName]; - } else if (!options.singleFileUploads && limit) { - fileSet = []; - paramNameSet = []; - for (i = 0; i < data.files.length; i += limit) { - fileSet.push(data.files.slice(i, i + limit)); - paramNameSlice = paramName.slice(i, i + limit); - if (!paramNameSlice.length) { - paramNameSlice = paramName; - } - paramNameSet.push(paramNameSlice); - } - } else { - paramNameSet = paramName; - } - data.originalFiles = data.files; - $.each(fileSet || data.files, function (index, element) { - var newData = $.extend({}, data); - newData.files = fileSet ? element : [element]; - newData.paramName = paramNameSet[index]; - newData.submit = function () { - newData.jqXHR = this.jqXHR = - (that._trigger('submit', e, this) !== false) && - that._onSend(e, this); - return this.jqXHR; - }; - return (result = that._trigger('add', e, newData)); - }); - return result; - }, - - // File Normalization for Gecko 1.9.1 (Firefox 3.5) support: - _normalizeFile: function (index, file) { - if (file.name === undefined && file.size === undefined) { - file.name = file.fileName; - file.size = file.fileSize; - } - }, - - _replaceFileInput: function (input) { - var inputClone = input.clone(true); - $('
      ').append(inputClone)[0].reset(); - // Detaching allows one to insert the fileInput on another form - // without loosing the file input value: - input.after(inputClone).detach(); - // Avoid memory leaks with the detached file input: - $.cleanData(input.unbind('remove')); - // Replace the original file input element in the fileInput - // collection with the clone, which has been copied including - // event handlers: - this.options.fileInput = this.options.fileInput.map(function (i, el) { - if (el === input[0]) { - return inputClone[0]; - } - return el; - }); - // If the widget has been initialized on the file input itself, - // override this.element with the file input clone: - if (input[0] === this.element[0]) { - this.element = inputClone; - } - }, - - _getFileInputFiles: function (fileInput) { - fileInput = $(fileInput); - var files = $.each($.makeArray(fileInput.prop('files')), this._normalizeFile), - value; - if (!files.length) { - value = fileInput.prop('value'); - if (!value) { - return []; - } - // If the files property is not available, the browser does not - // support the File API and we add a pseudo File object with - // the input value as name with path information removed: - files = [{name: value.replace(/^.*\\/, '')}]; - } - return files; - }, - - _onChange: function (e) { - var that = e.data.fileupload, - data = { - fileInput: $(e.target), - form: $(e.target.form) - }; - data.files = that._getFileInputFiles(data.fileInput); - if (that.options.replaceFileInput) { - that._replaceFileInput(data.fileInput); - } - if (that._trigger('change', e, data) === false || - that._onAdd(e, data) === false) { - return false; - } - }, - - _onPaste: function (e) { - var that = e.data.fileupload, - cbd = e.originalEvent.clipboardData, - items = (cbd && cbd.items) || [], - data = {files: []}; - $.each(items, function (index, item) { - var file = item.getAsFile && item.getAsFile(); - if (file) { - data.files.push(file); - } - }); - if (that._trigger('paste', e, data) === false || - that._onAdd(e, data) === false) { - return false; - } - }, - - _onDrop: function (e) { - var that = e.data.fileupload, - dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer, - data = { - files: $.each( - $.makeArray(dataTransfer && dataTransfer.files), - that._normalizeFile - ) - }; - if (that._trigger('drop', e, data) === false || - that._onAdd(e, data) === false) { - return false; - } - e.preventDefault(); - }, - - _onDragOver: function (e) { - var that = e.data.fileupload, - dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; - if (that._trigger('dragover', e) === false) { - return false; - } - if (dataTransfer) { - dataTransfer.dropEffect = 'copy'; - } - e.preventDefault(); - }, - - _initEventHandlers: function () { - var ns = this.options.namespace; - if (this._isXHRUpload(this.options)) { - this.options.dropZone - .bind('dragover.' + ns, {fileupload: this}, this._onDragOver) - .bind('drop.' + ns, {fileupload: this}, this._onDrop) - .bind('paste.' + ns, {fileupload: this}, this._onPaste); - } - this.options.fileInput - .bind('change.' + ns, {fileupload: this}, this._onChange); - }, - - _destroyEventHandlers: function () { - var ns = this.options.namespace; - this.options.dropZone - .unbind('dragover.' + ns, this._onDragOver) - .unbind('drop.' + ns, this._onDrop) - .unbind('paste.' + ns, this._onPaste); - this.options.fileInput - .unbind('change.' + ns, this._onChange); - }, - - _setOption: function (key, value) { - var refresh = $.inArray(key, this._refreshOptionsList) !== -1; - if (refresh) { - this._destroyEventHandlers(); - } - $.Widget.prototype._setOption.call(this, key, value); - if (refresh) { - this._initSpecialOptions(); - this._initEventHandlers(); - } - }, - - _initSpecialOptions: function () { - var options = this.options; - if (options.fileInput === undefined) { - options.fileInput = this.element.is('input:file') ? - this.element : this.element.find('input:file'); - } else if (!(options.fileInput instanceof $)) { - options.fileInput = $(options.fileInput); - } - if (!(options.dropZone instanceof $)) { - options.dropZone = $(options.dropZone); - } - }, - - _create: function () { - var options = this.options; - // Initialize options set via HTML5 data-attributes: - $.extend(options, $(this.element[0].cloneNode(false)).data()); - options.namespace = options.namespace || this.widgetName; - this._initSpecialOptions(); - this._slots = []; - this._sequence = this._getXHRPromise(true); - this._sending = this._active = this._loaded = this._total = 0; - this._initEventHandlers(); - }, - - destroy: function () { - this._destroyEventHandlers(); - $.Widget.prototype.destroy.call(this); - }, - - enable: function () { - $.Widget.prototype.enable.call(this); - this._initEventHandlers(); - }, - - disable: function () { - this._destroyEventHandlers(); - $.Widget.prototype.disable.call(this); - }, - - // This method is exposed to the widget API and allows adding files - // using the fileupload API. The data parameter accepts an object which - // must have a files property and can contain additional options: - // .fileupload('add', {files: filesList}); - add: function (data) { - if (!data || this.options.disabled) { - return; - } - if (data.fileInput && !data.files) { - data.files = this._getFileInputFiles(data.fileInput); - } else { - data.files = $.each($.makeArray(data.files), this._normalizeFile); - } - this._onAdd(null, data); - }, - - // This method is exposed to the widget API and allows sending files - // using the fileupload API. The data parameter accepts an object which - // must have a files property and can contain additional options: - // .fileupload('send', {files: filesList}); - // The method returns a Promise object for the file upload call. - send: function (data) { - if (data && !this.options.disabled) { - if (data.fileInput && !data.files) { - data.files = this._getFileInputFiles(data.fileInput); - } else { - data.files = $.each($.makeArray(data.files), this._normalizeFile); - } - if (data.files.length) { - return this._onSend(null, data); - } - } - return this._getXHRPromise(false, data && data.context); - } - - }); - -})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/js/jquery.iframe-transport.js --- a/OrthancExplorer/libs/jquery-file-upload/js/jquery.iframe-transport.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,171 +0,0 @@ -/* - * jQuery Iframe Transport Plugin 1.4 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2011, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/*jslint unparam: true, nomen: true */ -/*global define, window, document */ - -(function (factory) { - 'use strict'; - if (typeof define === 'function' && define.amd) { - // Register as an anonymous AMD module: - define(['jquery'], factory); - } else { - // Browser globals: - factory(window.jQuery); - } -}(function ($) { - 'use strict'; - - // Helper variable to create unique names for the transport iframes: - var counter = 0; - - // The iframe transport accepts three additional options: - // options.fileInput: a jQuery collection of file input fields - // options.paramName: the parameter name for the file form data, - // overrides the name property of the file input field(s), - // can be a string or an array of strings. - // options.formData: an array of objects with name and value properties, - // equivalent to the return data of .serializeArray(), e.g.: - // [{name: 'a', value: 1}, {name: 'b', value: 2}] - $.ajaxTransport('iframe', function (options) { - if (options.async && (options.type === 'POST' || options.type === 'GET')) { - var form, - iframe; - return { - send: function (_, completeCallback) { - form = $('
      '); - // javascript:false as initial iframe src - // prevents warning popups on HTTPS in IE6. - // IE versions below IE8 cannot set the name property of - // elements that have already been added to the DOM, - // so we set the name along with the iframe HTML markup: - iframe = $( - '' - ).bind('load', function () { - var fileInputClones, - paramNames = $.isArray(options.paramName) ? - options.paramName : [options.paramName]; - iframe - .unbind('load') - .bind('load', function () { - var response; - // Wrap in a try/catch block to catch exceptions thrown - // when trying to access cross-domain iframe contents: - try { - response = iframe.contents(); - // Google Chrome and Firefox do not throw an - // exception when calling iframe.contents() on - // cross-domain requests, so we unify the response: - if (!response.length || !response[0].firstChild) { - throw new Error(); - } - } catch (e) { - response = undefined; - } - // The complete callback returns the - // iframe content document as response object: - completeCallback( - 200, - 'success', - {'iframe': response} - ); - // Fix for IE endless progress bar activity bug - // (happens on form submits to iframe targets): - $('') - .appendTo(form); - form.remove(); - }); - form - .prop('target', iframe.prop('name')) - .prop('action', options.url) - .prop('method', options.type); - if (options.formData) { - $.each(options.formData, function (index, field) { - $('') - .prop('name', field.name) - .val(field.value) - .appendTo(form); - }); - } - if (options.fileInput && options.fileInput.length && - options.type === 'POST') { - fileInputClones = options.fileInput.clone(); - // Insert a clone for each file input field: - options.fileInput.after(function (index) { - return fileInputClones[index]; - }); - if (options.paramName) { - options.fileInput.each(function (index) { - $(this).prop( - 'name', - paramNames[index] || options.paramName - ); - }); - } - // Appending the file input fields to the hidden form - // removes them from their original location: - form - .append(options.fileInput) - .prop('enctype', 'multipart/form-data') - // enctype must be set as encoding for IE: - .prop('encoding', 'multipart/form-data'); - } - form.submit(); - // Insert the file input fields at their original location - // by replacing the clones with the originals: - if (fileInputClones && fileInputClones.length) { - options.fileInput.each(function (index, input) { - var clone = $(fileInputClones[index]); - $(input).prop('name', clone.prop('name')); - clone.replaceWith(input); - }); - } - }); - form.append(iframe).appendTo(document.body); - }, - abort: function () { - if (iframe) { - // javascript:false as iframe src aborts the request - // and prevents warning popups on HTTPS in IE6. - // concat is used to avoid the "Script URL" JSLint error: - iframe - .unbind('load') - .prop('src', 'javascript'.concat(':false;')); - } - if (form) { - form.remove(); - } - } - }; - } - }); - - // The iframe transport returns the iframe content document as response. - // The following adds converters from iframe to text, json, html, and script: - $.ajaxSetup({ - converters: { - 'iframe text': function (iframe) { - return $(iframe[0].body).text(); - }, - 'iframe json': function (iframe) { - return $.parseJSON($(iframe[0].body).text()); - }, - 'iframe html': function (iframe) { - return $(iframe[0].body).html(); - }, - 'iframe script': function (iframe) { - return $.globalEval($(iframe[0].body).text()); - } - } - }); - -})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/js/locale.js --- a/OrthancExplorer/libs/jquery-file-upload/js/locale.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -/* - * jQuery File Upload Plugin Localization Example 6.5.1 - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2012, Sebastian Tschan - * https://blueimp.net - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/MIT - */ - -/*global window */ - -window.locale = { - "fileupload": { - "errors": { - "maxFileSize": "File is too big", - "minFileSize": "File is too small", - "acceptFileTypes": "Filetype not allowed", - "maxNumberOfFiles": "Max number of files exceeded", - "uploadedBytes": "Uploaded bytes exceed file size", - "emptyResult": "Empty file upload result" - }, - "error": "Error", - "start": "Start", - "cancel": "Cancel", - "destroy": "Delete" - } -}; diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery-file-upload/js/vendor/jquery.ui.widget.js --- a/OrthancExplorer/libs/jquery-file-upload/js/vendor/jquery.ui.widget.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,282 +0,0 @@ -/* - * jQuery UI Widget 1.8.18+amd - * https://github.com/blueimp/jQuery-File-Upload - * - * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * http://docs.jquery.com/UI/Widget - */ - -(function (factory) { - if (typeof define === "function" && define.amd) { - // Register as an anonymous AMD module: - define(["jquery"], factory); - } else { - // Browser globals: - factory(jQuery); - } -}(function( $, undefined ) { - -// jQuery 1.4+ -if ( $.cleanData ) { - var _cleanData = $.cleanData; - $.cleanData = function( elems ) { - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - try { - $( elem ).triggerHandler( "remove" ); - // http://bugs.jquery.com/ticket/8235 - } catch( e ) {} - } - _cleanData( elems ); - }; -} else { - var _remove = $.fn.remove; - $.fn.remove = function( selector, keepData ) { - return this.each(function() { - if ( !keepData ) { - if ( !selector || $.filter( selector, [ this ] ).length ) { - $( "*", this ).add( [ this ] ).each(function() { - try { - $( this ).triggerHandler( "remove" ); - // http://bugs.jquery.com/ticket/8235 - } catch( e ) {} - }); - } - } - return _remove.call( $(this), selector, keepData ); - }); - }; -} - -$.widget = function( name, base, prototype ) { - var namespace = name.split( "." )[ 0 ], - fullName; - name = name.split( "." )[ 1 ]; - fullName = namespace + "-" + name; - - if ( !prototype ) { - prototype = base; - base = $.Widget; - } - - // create selector for plugin - $.expr[ ":" ][ fullName ] = function( elem ) { - return !!$.data( elem, name ); - }; - - $[ namespace ] = $[ namespace ] || {}; - $[ namespace ][ name ] = function( options, element ) { - // allow instantiation without initializing for simple inheritance - if ( arguments.length ) { - this._createWidget( options, element ); - } - }; - - var basePrototype = new base(); - // we need to make the options hash a property directly on the new instance - // otherwise we'll modify the options hash on the prototype that we're - // inheriting from -// $.each( basePrototype, function( key, val ) { -// if ( $.isPlainObject(val) ) { -// basePrototype[ key ] = $.extend( {}, val ); -// } -// }); - basePrototype.options = $.extend( true, {}, basePrototype.options ); - $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { - namespace: namespace, - widgetName: name, - widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, - widgetBaseClass: fullName - }, prototype ); - - $.widget.bridge( name, $[ namespace ][ name ] ); -}; - -$.widget.bridge = function( name, object ) { - $.fn[ name ] = function( options ) { - var isMethodCall = typeof options === "string", - args = Array.prototype.slice.call( arguments, 1 ), - returnValue = this; - - // allow multiple hashes to be passed on init - options = !isMethodCall && args.length ? - $.extend.apply( null, [ true, options ].concat(args) ) : - options; - - // prevent calls to internal methods - if ( isMethodCall && options.charAt( 0 ) === "_" ) { - return returnValue; - } - - if ( isMethodCall ) { - this.each(function() { - var instance = $.data( this, name ), - methodValue = instance && $.isFunction( instance[options] ) ? - instance[ options ].apply( instance, args ) : - instance; - // TODO: add this back in 1.9 and use $.error() (see #5972) -// if ( !instance ) { -// throw "cannot call methods on " + name + " prior to initialization; " + -// "attempted to call method '" + options + "'"; -// } -// if ( !$.isFunction( instance[options] ) ) { -// throw "no such method '" + options + "' for " + name + " widget instance"; -// } -// var methodValue = instance[ options ].apply( instance, args ); - if ( methodValue !== instance && methodValue !== undefined ) { - returnValue = methodValue; - return false; - } - }); - } else { - this.each(function() { - var instance = $.data( this, name ); - if ( instance ) { - instance.option( options || {} )._init(); - } else { - $.data( this, name, new object( options, this ) ); - } - }); - } - - return returnValue; - }; -}; - -$.Widget = function( options, element ) { - // allow instantiation without initializing for simple inheritance - if ( arguments.length ) { - this._createWidget( options, element ); - } -}; - -$.Widget.prototype = { - widgetName: "widget", - widgetEventPrefix: "", - options: { - disabled: false - }, - _createWidget: function( options, element ) { - // $.widget.bridge stores the plugin instance, but we do it anyway - // so that it's stored even before the _create function runs - $.data( element, this.widgetName, this ); - this.element = $( element ); - this.options = $.extend( true, {}, - this.options, - this._getCreateOptions(), - options ); - - var self = this; - this.element.bind( "remove." + this.widgetName, function() { - self.destroy(); - }); - - this._create(); - this._trigger( "create" ); - this._init(); - }, - _getCreateOptions: function() { - return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; - }, - _create: function() {}, - _init: function() {}, - - destroy: function() { - this.element - .unbind( "." + this.widgetName ) - .removeData( this.widgetName ); - this.widget() - .unbind( "." + this.widgetName ) - .removeAttr( "aria-disabled" ) - .removeClass( - this.widgetBaseClass + "-disabled " + - "ui-state-disabled" ); - }, - - widget: function() { - return this.element; - }, - - option: function( key, value ) { - var options = key; - - if ( arguments.length === 0 ) { - // don't return a reference to the internal hash - return $.extend( {}, this.options ); - } - - if (typeof key === "string" ) { - if ( value === undefined ) { - return this.options[ key ]; - } - options = {}; - options[ key ] = value; - } - - this._setOptions( options ); - - return this; - }, - _setOptions: function( options ) { - var self = this; - $.each( options, function( key, value ) { - self._setOption( key, value ); - }); - - return this; - }, - _setOption: function( key, value ) { - this.options[ key ] = value; - - if ( key === "disabled" ) { - this.widget() - [ value ? "addClass" : "removeClass"]( - this.widgetBaseClass + "-disabled" + " " + - "ui-state-disabled" ) - .attr( "aria-disabled", value ); - } - - return this; - }, - - enable: function() { - return this._setOption( "disabled", false ); - }, - disable: function() { - return this._setOption( "disabled", true ); - }, - - _trigger: function( type, event, data ) { - var prop, orig, - callback = this.options[ type ]; - - data = data || {}; - event = $.Event( event ); - event.type = ( type === this.widgetEventPrefix ? - type : - this.widgetEventPrefix + type ).toLowerCase(); - // the original event may come from any element - // so we need to reset the target on the new event - event.target = this.element[ 0 ]; - - // copy original event properties over to the new event - orig = event.originalEvent; - if ( orig ) { - for ( prop in orig ) { - if ( !( prop in event ) ) { - event[ prop ] = orig[ prop ]; - } - } - } - - this.element.trigger( event, data ); - - return !( $.isFunction(callback) && - callback.call( this.element[0], event, data ) === false || - event.isDefaultPrevented() ); - } -}; - -})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery.blockui.js --- a/OrthancExplorer/libs/jquery.blockui.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,499 +0,0 @@ -/*! - * jQuery blockUI plugin - * Version 2.39 (23-MAY-2011) - * @requires jQuery v1.2.3 or later - * - * Examples at: http://malsup.com/jquery/block/ - * Copyright (c) 2007-2010 M. Alsup - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * Thanks to Amir-Hossein Sobhi for some excellent contributions! - */ - -;(function($) { - -if (/1\.(0|1|2)\.(0|1|2)/.test($.fn.jquery) || /^1.1/.test($.fn.jquery)) { - alert('blockUI requires jQuery v1.2.3 or later! You are using v' + $.fn.jquery); - return; -} - -$.fn._fadeIn = $.fn.fadeIn; - -var noOp = function() {}; - -// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle -// retarded userAgent strings on Vista) -var mode = document.documentMode || 0; -var setExpr = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8); -var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent) && !mode; - -// global $ methods for blocking/unblocking the entire page -$.blockUI = function(opts) { install(window, opts); }; -$.unblockUI = function(opts) { remove(window, opts); }; - -// convenience method for quick growl-like notifications (http://www.google.com/search?q=growl) -$.growlUI = function(title, message, timeout, onClose) { - var $m = $('
      '); - if (title) $m.append('

      '+title+'

      '); - if (message) $m.append('

      '+message+'

      '); - if (timeout == undefined) timeout = 3000; - $.blockUI({ - message: $m, fadeIn: 700, fadeOut: 1000, centerY: false, - timeout: timeout, showOverlay: false, - onUnblock: onClose, - css: $.blockUI.defaults.growlCSS - }); -}; - -// plugin method for blocking element content -$.fn.block = function(opts) { - return this.unblock({ fadeOut: 0 }).each(function() { - if ($.css(this,'position') == 'static') - this.style.position = 'relative'; - if ($.browser.msie) - this.style.zoom = 1; // force 'hasLayout' - install(this, opts); - }); -}; - -// plugin method for unblocking element content -$.fn.unblock = function(opts) { - return this.each(function() { - remove(this, opts); - }); -}; - -$.blockUI.version = 2.39; // 2nd generation blocking at no extra cost! - -// override these in your code to change the default behavior and style -$.blockUI.defaults = { - // message displayed when blocking (use null for no message) - message: '

      Please wait...

      ', - - title: null, // title string; only used when theme == true - draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded) - - theme: false, // set to true to use with jQuery UI themes - - // styles for the message when blocking; if you wish to disable - // these and use an external stylesheet then do this in your code: - // $.blockUI.defaults.css = {}; - css: { - padding: 0, - margin: 0, - width: '30%', - top: '40%', - left: '35%', - textAlign: 'center', - color: '#000', - border: '3px solid #aaa', - backgroundColor:'#fff', - cursor: 'wait' - }, - - // minimal style set used when themes are used - themedCSS: { - width: '30%', - top: '40%', - left: '35%' - }, - - // styles for the overlay - overlayCSS: { - backgroundColor: '#000', - opacity: 0.6, - cursor: 'wait' - }, - - // styles applied when using $.growlUI - growlCSS: { - width: '350px', - top: '10px', - left: '', - right: '10px', - border: 'none', - padding: '5px', - opacity: 0.6, - cursor: 'default', - color: '#fff', - backgroundColor: '#000', - '-webkit-border-radius': '10px', - '-moz-border-radius': '10px', - 'border-radius': '10px' - }, - - // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w - // (hat tip to Jorge H. N. de Vasconcelos) - iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', - - // force usage of iframe in non-IE browsers (handy for blocking applets) - forceIframe: false, - - // z-index for the blocking overlay - baseZ: 1000, - - // set these to true to have the message automatically centered - centerX: true, // <-- only effects element blocking (page block controlled via css above) - centerY: true, - - // allow body element to be stetched in ie6; this makes blocking look better - // on "short" pages. disable if you wish to prevent changes to the body height - allowBodyStretch: true, - - // enable if you want key and mouse events to be disabled for content that is blocked - bindEvents: true, - - // be default blockUI will supress tab navigation from leaving blocking content - // (if bindEvents is true) - constrainTabKey: true, - - // fadeIn time in millis; set to 0 to disable fadeIn on block - fadeIn: 200, - - // fadeOut time in millis; set to 0 to disable fadeOut on unblock - fadeOut: 400, - - // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock - timeout: 0, - - // disable if you don't want to show the overlay - showOverlay: true, - - // if true, focus will be placed in the first available input field when - // page blocking - focusInput: true, - - // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity) - applyPlatformOpacityRules: true, - - // callback method invoked when fadeIn has completed and blocking message is visible - onBlock: null, - - // callback method invoked when unblocking has completed; the callback is - // passed the element that has been unblocked (which is the window object for page - // blocks) and the options that were passed to the unblock call: - // onUnblock(element, options) - onUnblock: null, - - // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493 - quirksmodeOffsetHack: 4, - - // class name of the message block - blockMsgClass: 'blockMsg' -}; - -// private data and functions follow... - -var pageBlock = null; -var pageBlockEls = []; - -function install(el, opts) { - var full = (el == window); - var msg = opts && opts.message !== undefined ? opts.message : undefined; - opts = $.extend({}, $.blockUI.defaults, opts || {}); - opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); - var css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); - var themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); - msg = msg === undefined ? opts.message : msg; - - // remove the current block (if there is one) - if (full && pageBlock) - remove(window, {fadeOut:0}); - - // if an existing element is being used as the blocking content then we capture - // its current place in the DOM (and current display style) so we can restore - // it when we unblock - if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { - var node = msg.jquery ? msg[0] : msg; - var data = {}; - $(el).data('blockUI.history', data); - data.el = node; - data.parent = node.parentNode; - data.display = node.style.display; - data.position = node.style.position; - if (data.parent) - data.parent.removeChild(node); - } - - $(el).data('blockUI.onUnblock', opts.onUnblock); - var z = opts.baseZ; - - // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform; - // layer1 is the iframe layer which is used to supress bleed through of underlying content - // layer2 is the overlay layer which has opacity and a wait cursor (by default) - // layer3 is the message content that is displayed while blocking - - var lyr1 = ($.browser.msie || opts.forceIframe) - ? $('') - : $(''); - - var lyr2 = opts.theme - ? $('') - : $(''); - - var lyr3, s; - if (opts.theme && full) { - s = ''; - } - else if (opts.theme) { - s = ''; - } - else if (full) { - s = ''; - } - else { - s = ''; - } - lyr3 = $(s); - - // if we have a message, style it - if (msg) { - if (opts.theme) { - lyr3.css(themedCSS); - lyr3.addClass('ui-widget-content'); - } - else - lyr3.css(css); - } - - // style the overlay - if (!opts.theme && (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform)))) - lyr2.css(opts.overlayCSS); - lyr2.css('position', full ? 'fixed' : 'absolute'); - - // make iframe layer transparent in IE - if ($.browser.msie || opts.forceIframe) - lyr1.css('opacity',0.0); - - //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el); - var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el); - $.each(layers, function() { - this.appendTo($par); - }); - - if (opts.theme && opts.draggable && $.fn.draggable) { - lyr3.draggable({ - handle: '.ui-dialog-titlebar', - cancel: 'li' - }); - } - - // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling) - var expr = setExpr && (!$.boxModel || $('object,embed', full ? null : el).length > 0); - if (ie6 || expr) { - // give body 100% height - if (full && opts.allowBodyStretch && $.boxModel) - $('html,body').css('height','100%'); - - // fix ie6 issue when blocked element has a border width - if ((ie6 || !$.boxModel) && !full) { - var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth'); - var fixT = t ? '(0 - '+t+')' : 0; - var fixL = l ? '(0 - '+l+')' : 0; - } - - // simulate fixed position - $.each([lyr1,lyr2,lyr3], function(i,o) { - var s = o[0].style; - s.position = 'absolute'; - if (i < 2) { - full ? s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"') - : s.setExpression('height','this.parentNode.offsetHeight + "px"'); - full ? s.setExpression('width','jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"') - : s.setExpression('width','this.parentNode.offsetWidth + "px"'); - if (fixL) s.setExpression('left', fixL); - if (fixT) s.setExpression('top', fixT); - } - else if (opts.centerY) { - if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); - s.marginTop = 0; - } - else if (!opts.centerY && full) { - var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0; - var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"'; - s.setExpression('top',expression); - } - }); - } - - // show the message - if (msg) { - if (opts.theme) - lyr3.find('.ui-widget-content').append(msg); - else - lyr3.append(msg); - if (msg.jquery || msg.nodeType) - $(msg).show(); - } - - if (($.browser.msie || opts.forceIframe) && opts.showOverlay) - lyr1.show(); // opacity is zero - if (opts.fadeIn) { - var cb = opts.onBlock ? opts.onBlock : noOp; - var cb1 = (opts.showOverlay && !msg) ? cb : noOp; - var cb2 = msg ? cb : noOp; - if (opts.showOverlay) - lyr2._fadeIn(opts.fadeIn, cb1); - if (msg) - lyr3._fadeIn(opts.fadeIn, cb2); - } - else { - if (opts.showOverlay) - lyr2.show(); - if (msg) - lyr3.show(); - if (opts.onBlock) - opts.onBlock(); - } - - // bind key and mouse events - bind(1, el, opts); - - if (full) { - pageBlock = lyr3[0]; - pageBlockEls = $(':input:enabled:visible',pageBlock); - if (opts.focusInput) - setTimeout(focus, 20); - } - else - center(lyr3[0], opts.centerX, opts.centerY); - - if (opts.timeout) { - // auto-unblock - var to = setTimeout(function() { - full ? $.unblockUI(opts) : $(el).unblock(opts); - }, opts.timeout); - $(el).data('blockUI.timeout', to); - } -}; - -// remove the block -function remove(el, opts) { - var full = (el == window); - var $el = $(el); - var data = $el.data('blockUI.history'); - var to = $el.data('blockUI.timeout'); - if (to) { - clearTimeout(to); - $el.removeData('blockUI.timeout'); - } - opts = $.extend({}, $.blockUI.defaults, opts || {}); - bind(0, el, opts); // unbind events - - if (opts.onUnblock === null) { - opts.onUnblock = $el.data('blockUI.onUnblock'); - $el.removeData('blockUI.onUnblock'); - } - - var els; - if (full) // crazy selector to handle odd field errors in ie6/7 - els = $('body').children().filter('.blockUI').add('body > .blockUI'); - else - els = $('.blockUI', el); - - if (full) - pageBlock = pageBlockEls = null; - - if (opts.fadeOut) { - els.fadeOut(opts.fadeOut); - setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut); - } - else - reset(els, data, opts, el); -}; - -// move blocking element back into the DOM where it started -function reset(els,data,opts,el) { - els.each(function(i,o) { - // remove via DOM calls so we don't lose event handlers - if (this.parentNode) - this.parentNode.removeChild(this); - }); - - if (data && data.el) { - data.el.style.display = data.display; - data.el.style.position = data.position; - if (data.parent) - data.parent.appendChild(data.el); - $(el).removeData('blockUI.history'); - } - - if (typeof opts.onUnblock == 'function') - opts.onUnblock(el,opts); -}; - -// bind/unbind the handler -function bind(b, el, opts) { - var full = el == window, $el = $(el); - - // don't bother unbinding if there is nothing to unbind - if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) - return; - if (!full) - $el.data('blockUI.isBlocked', b); - - // don't bind events when overlay is not in use or if bindEvents is false - if (!opts.bindEvents || (b && !opts.showOverlay)) - return; - - // bind anchors and inputs for mouse and key events - var events = 'mousedown mouseup keydown keypress'; - b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler); - -// former impl... -// var $e = $('a,:input'); -// b ? $e.bind(events, opts, handler) : $e.unbind(events, handler); -}; - -// event handler to suppress keyboard/mouse events when blocking -function handler(e) { - // allow tab navigation (conditionally) - if (e.keyCode && e.keyCode == 9) { - if (pageBlock && e.data.constrainTabKey) { - var els = pageBlockEls; - var fwd = !e.shiftKey && e.target === els[els.length-1]; - var back = e.shiftKey && e.target === els[0]; - if (fwd || back) { - setTimeout(function(){focus(back)},10); - return false; - } - } - } - var opts = e.data; - // allow events within the message content - if ($(e.target).parents('div.' + opts.blockMsgClass).length > 0) - return true; - - // allow events for content that is not being blocked - return $(e.target).parents().children().filter('div.blockUI').length == 0; -}; - -function focus(back) { - if (!pageBlockEls) - return; - var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0]; - if (e) - e.focus(); -}; - -function center(el, x, y) { - var p = el.parentNode, s = el.style; - var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth'); - var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth'); - if (x) s.left = l > 0 ? (l+'px') : '0'; - if (y) s.top = t > 0 ? (t+'px') : '0'; -}; - -function sz(el, p) { - return parseInt($.css(el,p))||0; -}; - -})(jQuery); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery.min.js --- a/OrthancExplorer/libs/jquery.min.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -/*! jQuery v1.7.2 jquery.com | jquery.org/license */ -(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
      a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
      "+""+"
      ",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
      t
      ",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
      ",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( -a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

      ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
      ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
      ","
      "]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f -.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
      ").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery.mobile.min.css --- a/OrthancExplorer/libs/jquery.mobile.min.css Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ -.ui-bar-a{border:1px solid #333;background:#111;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #000;background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#111));background-image:-webkit-linear-gradient(#3c3c3c,#111);background-image:-moz-linear-gradient(#3c3c3c,#111);background-image:-ms-linear-gradient(#3c3c3c,#111);background-image:-o-linear-gradient(#3c3c3c,#111);background-image:linear-gradient(#3c3c3c,#111)}.ui-bar-a,.ui-bar-a input,.ui-bar-a select,.ui-bar-a textarea,.ui-bar-a button{font-family:Helvetica,Arial,sans-serif}.ui-bar-a .ui-link-inherit{color:#fff}.ui-bar-a .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-a .ui-link:hover{color:#2489ce}.ui-bar-a .ui-link:active{color:#2489ce}.ui-bar-a .ui-link:visited{color:#2489ce}.ui-body-a,.ui-overlay-a{border:1px solid #444;background:#222;color:#fff;text-shadow:0 1px 1px #111;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#222));background-image:-webkit-linear-gradient(#444,#222);background-image:-moz-linear-gradient(#444,#222);background-image:-ms-linear-gradient(#444,#222);background-image:-o-linear-gradient(#444,#222);background-image:linear-gradient(#444,#222)}.ui-overlay-a{background-image:none;border-width:0}.ui-body-a,.ui-body-a input,.ui-body-a select,.ui-body-a textarea,.ui-body-a button{font-family:Helvetica,Arial,sans-serif}.ui-body-a .ui-link-inherit{color:#fff}.ui-body-a .ui-link{color:#2489ce;font-weight:bold}.ui-body-a .ui-link:hover{color:#2489ce}.ui-body-a .ui-link:active{color:#2489ce}.ui-body-a .ui-link:visited{color:#2489ce}.ui-btn-up-a{border:1px solid #111;background:#333;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#2d2d2d));background-image:-webkit-linear-gradient(#444,#2d2d2d);background-image:-moz-linear-gradient(#444,#2d2d2d);background-image:-ms-linear-gradient(#444,#2d2d2d);background-image:-o-linear-gradient(#444,#2d2d2d);background-image:linear-gradient(#444,#2d2d2d)}.ui-btn-up-a a.ui-link-inherit{color:#fff}.ui-btn-hover-a{border:1px solid #000;background:#444;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#555),to(#383838));background-image:-webkit-linear-gradient(#555,#383838);background-image:-moz-linear-gradient(#555,#383838);background-image:-ms-linear-gradient(#555,#383838);background-image:-o-linear-gradient(#555,#383838);background-image:linear-gradient(#555,#383838)}.ui-btn-hover-a a.ui-link-inherit{color:#fff}.ui-btn-down-a{border:1px solid #000;background:#222;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#202020),to(#2c2c2c));background-image:-webkit-linear-gradient(#202020,#2c2c2c);background-image:-moz-linear-gradient(#202020,#2c2c2c);background-image:-ms-linear-gradient(#202020,#2c2c2c);background-image:-o-linear-gradient(#202020,#2c2c2c);background-image:linear-gradient(#202020,#2c2c2c)}.ui-btn-down-a a.ui-link-inherit{color:#fff}.ui-btn-up-a,.ui-btn-hover-a,.ui-btn-down-a{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-b{border:1px solid #456f9a;background:#5e87b0;color:#fff;font-weight:bold;text-shadow:0 1px 1px #3e6790;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#497bae));background-image:-webkit-linear-gradient(#6facd5,#497bae);background-image:-moz-linear-gradient(#6facd5,#497bae);background-image:-ms-linear-gradient(#6facd5,#497bae);background-image:-o-linear-gradient(#6facd5,#497bae);background-image:linear-gradient(#6facd5,#497bae)}.ui-bar-b,.ui-bar-b input,.ui-bar-b select,.ui-bar-b textarea,.ui-bar-b button{font-family:Helvetica,Arial,sans-serif}.ui-bar-b .ui-link-inherit{color:#fff}.ui-bar-b .ui-link{color:#ddf0f8;font-weight:bold}.ui-bar-b .ui-link:hover{color:#ddf0f8}.ui-bar-b .ui-link:active{color:#ddf0f8}.ui-bar-b .ui-link:visited{color:#ddf0f8}.ui-body-b,.ui-overlay-b{border:1px solid #999;background:#f3f3f3;color:#222;text-shadow:0 1px 0 #fff;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#ccc));background-image:-webkit-linear-gradient(#ddd,#ccc);background-image:-moz-linear-gradient(#ddd,#ccc);background-image:-ms-linear-gradient(#ddd,#ccc);background-image:-o-linear-gradient(#ddd,#ccc);background-image:linear-gradient(#ddd,#ccc)}.ui-overlay-b{background-image:none;border-width:0}.ui-body-b,.ui-body-b input,.ui-body-b select,.ui-body-b textarea,.ui-body-b button{font-family:Helvetica,Arial,sans-serif}.ui-body-b .ui-link-inherit{color:#333}.ui-body-b .ui-link{color:#2489ce;font-weight:bold}.ui-body-b .ui-link:hover{color:#2489ce}.ui-body-b .ui-link:active{color:#2489ce}.ui-body-b .ui-link:visited{color:#2489ce}.ui-btn-up-b{border:1px solid #044062;background:#396b9e;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#5f9cc5),to(#396b9e));background-image:-webkit-linear-gradient(#5f9cc5,#396b9e);background-image:-moz-linear-gradient(#5f9cc5,#396b9e);background-image:-ms-linear-gradient(#5f9cc5,#396b9e);background-image:-o-linear-gradient(#5f9cc5,#396b9e);background-image:linear-gradient(#5f9cc5,#396b9e)}.ui-btn-up-b a.ui-link-inherit{color:#fff}.ui-btn-hover-b{border:1px solid #00415e;background:#4b88b6;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#4272a4));background-image:-webkit-linear-gradient(#6facd5,#4272a4);background-image:-moz-linear-gradient(#6facd5,#4272a4);background-image:-ms-linear-gradient(#6facd5,#4272a4);background-image:-o-linear-gradient(#6facd5,#4272a4);background-image:linear-gradient(#6facd5,#4272a4)}.ui-btn-hover-b a.ui-link-inherit{color:#fff}.ui-btn-down-b{border:1px solid #225377;background:#4e89c5;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#295b8e),to(#3e79b5));background-image:-webkit-linear-gradient(#295b8e,#3e79b5);background-image:-moz-linear-gradient(#295b8e,#3e79b5);background-image:-ms-linear-gradient(#295b8e,#3e79b5);background-image:-o-linear-gradient(#295b8e,#3e79b5);background-image:linear-gradient(#295b8e,#3e79b5)}.ui-btn-down-b a.ui-link-inherit{color:#fff}.ui-btn-up-b,.ui-btn-hover-b,.ui-btn-down-b{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-c{border:1px solid #b3b3b3;background:#eee;color:#3e3e3e;font-weight:bold;text-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#ddd));background-image:-webkit-linear-gradient(#f0f0f0,#ddd);background-image:-moz-linear-gradient(#f0f0f0,#ddd);background-image:-ms-linear-gradient(#f0f0f0,#ddd);background-image:-o-linear-gradient(#f0f0f0,#ddd);background-image:linear-gradient(#f0f0f0,#ddd)}.ui-bar-c .ui-link-inherit{color:#3e3e3e}.ui-bar-c .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-c .ui-link:hover{color:#2489ce}.ui-bar-c .ui-link:active{color:#2489ce}.ui-bar-c .ui-link:visited{color:#2489ce}.ui-bar-c,.ui-bar-c input,.ui-bar-c select,.ui-bar-c textarea,.ui-bar-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c,.ui-overlay-c{border:1px solid #aaa;color:#333;text-shadow:0 1px 0 #fff;background:#f9f9f9;background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#eee));background-image:-webkit-linear-gradient(#f9f9f9,#eee);background-image:-moz-linear-gradient(#f9f9f9,#eee);background-image:-ms-linear-gradient(#f9f9f9,#eee);background-image:-o-linear-gradient(#f9f9f9,#eee);background-image:linear-gradient(#f9f9f9,#eee)}.ui-overlay-c{background-image:none;border-width:0}.ui-body-c,.ui-body-c input,.ui-body-c select,.ui-body-c textarea,.ui-body-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c .ui-link-inherit{color:#333}.ui-body-c .ui-link{color:#2489ce;font-weight:bold}.ui-body-c .ui-link:hover{color:#2489ce}.ui-body-c .ui-link:active{color:#2489ce}.ui-body-c .ui-link:visited{color:#2489ce}.ui-btn-up-c{border:1px solid #ccc;background:#eee;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f1f1f1));background-image:-webkit-linear-gradient(#fff,#f1f1f1);background-image:-moz-linear-gradient(#fff,#f1f1f1);background-image:-ms-linear-gradient(#fff,#f1f1f1);background-image:-o-linear-gradient(#fff,#f1f1f1);background-image:linear-gradient(#fff,#f1f1f1)}.ui-btn-up-c a.ui-link-inherit{color:#2f3e46}.ui-btn-hover-c{border:1px solid #bbb;background:#dfdfdf;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f6f6f6),to(#e0e0e0));background-image:-webkit-linear-gradient(#f9f9f9,#e0e0e0);background-image:-moz-linear-gradient(#f6f6f6,#e0e0e0);background-image:-ms-linear-gradient(#f6f6f6,#e0e0e0);background-image:-o-linear-gradient(#f6f6f6,#e0e0e0);background-image:linear-gradient(#f6f6f6,#e0e0e0)}.ui-btn-hover-c a.ui-link-inherit{color:#2f3e46}.ui-btn-down-c{border:1px solid #bbb;background:#d6d6d6;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#d0d0d0),to(#dfdfdf));background-image:-webkit-linear-gradient(#d0d0d0,#dfdfdf);background-image:-moz-linear-gradient(#d0d0d0,#dfdfdf);background-image:-ms-linear-gradient(#d0d0d0,#dfdfdf);background-image:-o-linear-gradient(#d0d0d0,#dfdfdf);background-image:linear-gradient(#d0d0d0,#dfdfdf)}.ui-btn-down-c a.ui-link-inherit{color:#2f3e46}.ui-btn-up-c,.ui-btn-hover-c,.ui-btn-down-c{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-d{border:1px solid #bbb;background:#bbb;color:#333;text-shadow:0 1px 0 #eee;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#bbb));background-image:-webkit-linear-gradient(#ddd,#bbb);background-image:-moz-linear-gradient(#ddd,#bbb);background-image:-ms-linear-gradient(#ddd,#bbb);background-image:-o-linear-gradient(#ddd,#bbb);background-image:linear-gradient(#ddd,#bbb)}.ui-bar-d,.ui-bar-d input,.ui-bar-d select,.ui-bar-d textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-d .ui-link-inherit{color:#333}.ui-bar-d .ui-link{color:#2489ce;font-weight:bold}.ui-bar-d .ui-link:hover{color:#2489ce}.ui-bar-d .ui-link:active{color:#2489ce}.ui-bar-d .ui-link:visited{color:#2489ce}.ui-body-d,.ui-overlay-d{border:1px solid #bbb;color:#333;text-shadow:0 1px 0 #fff;background:#fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background-image:-webkit-linear-gradient(#fff,#fff);background-image:-moz-linear-gradient(#fff,#fff);background-image:-ms-linear-gradient(#fff,#fff);background-image:-o-linear-gradient(#fff,#fff);background-image:linear-gradient(#fff,#fff)}.ui-overlay-d{background-image:none;border-width:0}.ui-body-d,.ui-body-d input,.ui-body-d select,.ui-body-d textarea,.ui-body-d button{font-family:Helvetica,Arial,sans-serif}.ui-body-d .ui-link-inherit{color:#333}.ui-body-d .ui-link{color:#2489ce;font-weight:bold}.ui-body-d .ui-link:hover{color:#2489ce}.ui-body-d .ui-link:active{color:#2489ce}.ui-body-d .ui-link:visited{color:#2489ce}.ui-btn-up-d{border:1px solid #bbb;background:#fff;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#f6f6f6));background-image:-webkit-linear-gradient(#fafafa,#f6f6f6);background-image:-moz-linear-gradient(#fafafa,#f6f6f6);background-image:-ms-linear-gradient(#fafafa,#f6f6f6);background-image:-o-linear-gradient(#fafafa,#f6f6f6);background-image:linear-gradient(#fafafa,#f6f6f6)}.ui-btn-up-d a.ui-link-inherit{color:#333}.ui-btn-hover-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;cursor:pointer;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#fff));background-image:-webkit-linear-gradient(#eee,#fff);background-image:-moz-linear-gradient(#eee,#fff);background-image:-ms-linear-gradient(#eee,#fff);background-image:-o-linear-gradient(#eee,#fff);background-image:linear-gradient(#eee,#fff)}.ui-btn-hover-d a.ui-link-inherit{color:#333}.ui-btn-down-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#e5e5e5),to(#f2f2f2));background-image:-webkit-linear-gradient(#e5e5e5,#f2f2f2);background-image:-moz-linear-gradient(#e5e5e5,#f2f2f2);background-image:-ms-linear-gradient(#e5e5e5,#f2f2f2);background-image:-o-linear-gradient(#e5e5e5,#f2f2f2);background-image:linear-gradient(#e5e5e5,#f2f2f2)}.ui-btn-down-d a.ui-link-inherit{color:#333}.ui-btn-up-d,.ui-btn-hover-d,.ui-btn-down-d{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-e{border:1px solid #f7c942;background:#fadb4e;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fceda7),to(#fbef7e));background-image:-webkit-linear-gradient(#fceda7,#fbef7e);background-image:-moz-linear-gradient(#fceda7,#fbef7e);background-image:-ms-linear-gradient(#fceda7,#fbef7e);background-image:-o-linear-gradient(#fceda7,#fbef7e);background-image:linear-gradient(#fceda7,#fbef7e)}.ui-bar-e,.ui-bar-e input,.ui-bar-e select,.ui-bar-e textarea,.ui-bar-e button{font-family:Helvetica,Arial,sans-serif}.ui-bar-e .ui-link-inherit{color:#333}.ui-bar-e .ui-link{color:#2489ce;font-weight:bold}.ui-bar-e .ui-link:hover{color:#2489ce}.ui-bar-e .ui-link:active{color:#2489ce}.ui-bar-e .ui-link:visited{color:#2489ce}.ui-body-e,.ui-overlay-e{border:1px solid #f7c942;color:#222;text-shadow:0 1px 0 #fff;background:#fff9df;background-image:-webkit-gradient(linear,left top,left bottom,from(#fffadf),to(#fff3a5));background-image:-webkit-linear-gradient(#fffadf,#fff3a5);background-image:-moz-linear-gradient(#fffadf,#fff3a5);background-image:-ms-linear-gradient(#fffadf,#fff3a5);background-image:-o-linear-gradient(#fffadf,#fff3a5);background-image:linear-gradient(#fffadf,#fff3a5)}.ui-overlay-e{background-image:none;border-width:0}.ui-body-e,.ui-body-e input,.ui-body-e select,.ui-body-e textarea,.ui-body-e button{font-family:Helvetica,Arial,sans-serif}.ui-body-e .ui-link-inherit{color:#333}.ui-body-e .ui-link{color:#2489ce;font-weight:bold}.ui-body-e .ui-link:hover{color:#2489ce}.ui-body-e .ui-link:active{color:#2489ce}.ui-body-e .ui-link:visited{color:#2489ce}.ui-btn-up-e{border:1px solid #f4c63f;background:#fadb4e;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#ffefaa),to(#ffe155));background-image:-webkit-linear-gradient(#ffefaa,#ffe155);background-image:-moz-linear-gradient(#ffefaa,#ffe155);background-image:-ms-linear-gradient(#ffefaa,#ffe155);background-image:-o-linear-gradient(#ffefaa,#ffe155);background-image:linear-gradient(#ffefaa,#ffe155)}.ui-btn-up-e a.ui-link-inherit{color:#222}.ui-btn-hover-e{border:1px solid #f2c43d;background:#fbe26f;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff5ba),to(#fbdd52));background-image:-webkit-linear-gradient(#fff5ba,#fbdd52);background-image:-moz-linear-gradient(#fff5ba,#fbdd52);background-image:-ms-linear-gradient(#fff5ba,#fbdd52);background-image:-o-linear-gradient(#fff5ba,#fbdd52);background-image:linear-gradient(#fff5ba,#fbdd52)}.ui-btn-hover-e a.ui-link-inherit{color:#333}.ui-btn-down-e{border:1px solid #f2c43d;background:#fceda7;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8d94c),to(#fadb4e));background-image:-webkit-linear-gradient(#f8d94c,#fadb4e);background-image:-moz-linear-gradient(#f8d94c,#fadb4e);background-image:-ms-linear-gradient(#f8d94c,#fadb4e);background-image:-o-linear-gradient(#f8d94c,#fadb4e);background-image:linear-gradient(#f8d94c,#fadb4e)}.ui-btn-down-e a.ui-link-inherit{color:#333}.ui-btn-up-e,.ui-btn-hover-e,.ui-btn-down-e{font-family:Helvetica,Arial,sans-serif;text-decoration:none}a.ui-link-inherit{text-decoration:none!important}.ui-btn-active{border:1px solid #2373a5;background:#5393c5;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 1px 1px #3373a5;text-decoration:none;background-image:-webkit-gradient(linear,left top,left bottom,from(#5393c5),to(#6facd5));background-image:-webkit-linear-gradient(#5393c5,#6facd5);background-image:-moz-linear-gradient(#5393c5,#6facd5);background-image:-ms-linear-gradient(#5393c5,#6facd5);background-image:-o-linear-gradient(#5393c5,#6facd5);background-image:linear-gradient(#5393c5,#6facd5);font-family:Helvetica,Arial,sans-serif}.ui-btn-active a.ui-link-inherit{color:#fff}.ui-btn-inner{border-top:1px solid #fff;border-color:rgba(255,255,255,.3)}.ui-corner-tl{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em}.ui-corner-tr{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bl{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-br{-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-top{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bottom{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-right{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-left{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-all{-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.ui-corner-none{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.ui-br{border-bottom:#828282;border-bottom:rgba(130,130,130,.3);border-bottom-width:1px;border-bottom-style:solid}.ui-disabled{opacity:.3}.ui-disabled,.ui-disabled a{cursor:default!important;pointer-events:none}.ui-disabled .ui-btn-text{-ms-filter:"alpha(opacity=30)";filter:alpha(opacity=30);zoom:1}.ui-icon,.ui-icon-searchfield:after{background:#666;background:rgba(0,0,0,.4);background-image:url(images/icons-18-white.png);background-repeat:no-repeat;-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-alt{background:#fff;background:rgba(255,255,255,.3);background-image:url(images/icons-18-black.png);background-repeat:no-repeat}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-resolution:240dpi){.ui-icon-plus,.ui-icon-minus,.ui-icon-delete,.ui-icon-arrow-r,.ui-icon-arrow-l,.ui-icon-arrow-u,.ui-icon-arrow-d,.ui-icon-check,.ui-icon-gear,.ui-icon-refresh,.ui-icon-forward,.ui-icon-back,.ui-icon-grid,.ui-icon-star,.ui-icon-alert,.ui-icon-info,.ui-icon-home,.ui-icon-search,.ui-icon-searchfield:after,.ui-icon-checkbox-off,.ui-icon-checkbox-on,.ui-icon-radio-off,.ui-icon-radio-on{background-image:url(images/icons-36-white.png);-moz-background-size:776px 18px;-o-background-size:776px 18px;-webkit-background-size:776px 18px;background-size:776px 18px}.ui-icon-alt{background-image:url(images/icons-36-black.png)}}.ui-icon-plus{background-position:-0 50%}.ui-icon-minus{background-position:-36px 50%}.ui-icon-delete{background-position:-72px 50%}.ui-icon-arrow-r{background-position:-108px 50%}.ui-icon-arrow-l{background-position:-144px 50%}.ui-icon-arrow-u{background-position:-180px 50%}.ui-icon-arrow-d{background-position:-216px 50%}.ui-icon-check{background-position:-252px 50%}.ui-icon-gear{background-position:-288px 50%}.ui-icon-refresh{background-position:-324px 50%}.ui-icon-forward{background-position:-360px 50%}.ui-icon-back{background-position:-396px 50%}.ui-icon-grid{background-position:-432px 50%}.ui-icon-star{background-position:-468px 50%}.ui-icon-alert{background-position:-504px 50%}.ui-icon-info{background-position:-540px 50%}.ui-icon-home{background-position:-576px 50%}.ui-icon-search,.ui-icon-searchfield:after{background-position:-612px 50%}.ui-icon-checkbox-off{background-position:-684px 50%}.ui-icon-checkbox-on{background-position:-648px 50%}.ui-icon-radio-off{background-position:-756px 50%}.ui-icon-radio-on{background-position:-720px 50%}.ui-checkbox .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.ui-icon-checkbox-off,.ui-icon-radio-off{background-color:transparent}.ui-checkbox-on .ui-icon,.ui-radio-on .ui-icon{background-color:#4596ce}.ui-icon-loading{background:url(images/ajax-loader.gif);background-size:46px 46px}.ui-btn-corner-tl{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em}.ui-btn-corner-tr{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bl{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-br{-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-top{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bottom{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-right{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-left{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-all{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em}.ui-corner-tl,.ui-corner-tr,.ui-corner-bl,.ui-corner-br,.ui-corner-top,.ui-corner-bottom,.ui-corner-right,.ui-corner-left,.ui-corner-all,.ui-btn-corner-tl,.ui-btn-corner-tr,.ui-btn-corner-bl,.ui-btn-corner-br,.ui-btn-corner-top,.ui-btn-corner-bottom,.ui-btn-corner-right,.ui-btn-corner-left,.ui-btn-corner-all{-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.ui-overlay{background:#666;opacity:.5;filter:Alpha(Opacity=50);position:absolute;width:100%;height:100%}.ui-overlay-shadow{-moz-box-shadow:0 0 12px rgba(0,0,0,.6);-webkit-box-shadow:0 0 12px rgba(0,0,0,.6);box-shadow:0 0 12px rgba(0,0,0,.6)}.ui-shadow{-moz-box-shadow:0 1px 4px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 4px rgba(0,0,0,.3);box-shadow:0 1px 4px rgba(0,0,0,.3)}.ui-bar-a .ui-shadow,.ui-bar-b .ui-shadow,.ui-bar-c .ui-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.ui-shadow-inset{-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);box-shadow:inset 0 1px 4px rgba(0,0,0,.2)}.ui-icon-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.4);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.4);box-shadow:0 1px 0 rgba(255,255,255,.4)}.ui-btn:focus{outline:0}.ui-focus,.ui-btn:focus{-moz-box-shadow:0 0 12px #387bbe;-webkit-box-shadow:0 0 12px #387bbe;box-shadow:0 0 12px #387bbe}.ui-mobile-nosupport-boxshadow *{-moz-box-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}.ui-mobile-nosupport-boxshadow .ui-focus,.ui-mobile-nosupport-boxshadow .ui-btn:focus{outline-width:1px;outline-style:dotted}.ui-mobile,.ui-mobile body{height:99.9%}.ui-mobile fieldset,.ui-page{padding:0;margin:0}.ui-mobile a img,.ui-mobile fieldset{border-width:0}.ui-mobile-viewport{margin:0;overflow-x:visible;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}body.ui-mobile-viewport,div.ui-mobile-viewport{overflow-x:hidden}.ui-mobile [data-role=page],.ui-mobile [data-role=dialog],.ui-page{top:0;left:0;width:100%;min-height:100%;position:absolute;display:none;border:0}.ui-mobile .ui-page-active{display:block;overflow:visible}.ui-page{outline:0}@media screen and (orientation:portrait){.ui-mobile,.ui-mobile .ui-page{min-height:420px}}@media screen and (orientation:landscape){.ui-mobile,.ui-mobile .ui-page{min-height:300px}}.ui-loading .ui-loader{display:block}.ui-loader{display:none;z-index:9999999;position:fixed;top:50%;box-shadow:0 1px 1px -1px #fff;left:50%;border:0}.ui-loader-default{background:0;opacity:.18;width:46px;height:46px;margin-left:-23px;margin-top:-23px}.ui-loader-verbose{width:200px;opacity:.88;height:auto;margin-left:-110px;margin-top:-43px;padding:10px}.ui-loader-default h1{font-size:0;width:0;height:0;overflow:hidden}.ui-loader-verbose h1{font-size:16px;margin:0;text-align:center}.ui-loader .ui-icon{background-color:#000;display:block;margin:0;width:44px;height:44px;padding:1px;-webkit-border-radius:36px;-moz-border-radius:36px;border-radius:36px}.ui-loader-verbose .ui-icon{margin:0 auto 10px;opacity:.75}.ui-loader-textonly{padding:15px;margin-left:-115px}.ui-loader-textonly .ui-icon{display:none}.ui-loader-fakefix{position:absolute}.ui-mobile-rendering>*{visibility:hidden}.ui-bar,.ui-body{position:relative;padding:.4em 15px;overflow:hidden;display:block;clear:both}.ui-bar{font-size:16px;margin:0}.ui-bar h1,.ui-bar h2,.ui-bar h3,.ui-bar h4,.ui-bar h5,.ui-bar h6{margin:0;padding:0;font-size:16px;display:inline-block}.ui-header,.ui-footer{position:relative;border-left-width:0;border-right-width:0}.ui-header .ui-btn-left,.ui-header .ui-btn-right,.ui-footer .ui-btn-left,.ui-footer .ui-btn-right{position:absolute;top:3px}.ui-header .ui-btn-left,.ui-footer .ui-btn-left{left:5px}.ui-header .ui-btn-right,.ui-footer .ui-btn-right{right:5px}.ui-footer .ui-btn-icon-notext,.ui-header .ui-btn-icon-notext{top:6px}.ui-header .ui-title,.ui-footer .ui-title{min-height:1.1em;text-align:center;font-size:16px;display:block;margin:.6em 30% .8em;padding:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;outline:0!important}.ui-footer .ui-title{margin:.6em 15px .8em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-icon{width:18px;height:18px}.ui-nojs{position:absolute;left:-9999px}.ui-hide-label label,.ui-hidden-accessible{position:absolute!important;left:-9999px;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ui-mobile-viewport-transitioning,.ui-mobile-viewport-transitioning .ui-page{width:100%;height:100%;overflow:hidden}.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.out{-webkit-animation-timing-function:ease-in;-webkit-animation-duration:225ms;-moz-animation-timing-function:ease-in;-moz-animation-duration:225}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-moz-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadeout{from{opacity:1}to{opacity:0}}@-moz-keyframes fadeout{from{opacity:1}to{opacity:0}}.fade.out{opacity:0;-webkit-animation-duration:125ms;-webkit-animation-name:fadeout;-moz-animation-duration:125ms;-moz-animation-name:fadeout}.fade.in{opacity:1;-webkit-animation-duration:225ms;-webkit-animation-name:fadein;-moz-animation-duration:225ms;-moz-animation-name:fadein}.pop{-webkit-transform-origin:50% 50%;-moz-transform-origin:50% 50%}.pop.in{-webkit-transform:scale(1);-moz-transform:scale(1);opacity:1;-webkit-animation-name:popin;-moz-animation-name:popin;-webkit-animation-duration:350ms;-moz-animation-duration:350ms}.pop.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;opacity:0;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.pop.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein}.pop.out.reverse{-webkit-transform:scale(.8);-moz-transform:scale(.8);-webkit-animation-name:popout;-moz-animation-name:popout}@-webkit-keyframes popin{from{-webkit-transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-moz-keyframes popin{from{-moz-transform:scale(.8);opacity:0}to{-moz-transform:scale(1);opacity:1}}@-webkit-keyframes popout{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(.8);opacity:0}}@-moz-keyframes popout{from{-moz-transform:scale(1);opacity:1}to{-moz-transform:scale(.8);opacity:0}}@-webkit-keyframes slideinfromright{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromright{from{-moz-transform:translateX(100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideinfromleft{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromleft{from{-moz-transform:translateX(-100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideouttoleft{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(-100%)}}@-moz-keyframes slideouttoleft{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(-100%)}}@-webkit-keyframes slideouttoright{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(100%)}}@-moz-keyframes slideouttoright{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(100%)}}.slide.out,.slide.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.slide.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright;-moz-transform:translateX(0);-moz-animation-name:slideinfromright}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft;-moz-transform:translateX(0);-moz-animation-name:slideinfromleft}.slidefade.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft;-webkit-animation-duration:225ms;-moz-animation-duration:225ms}.slidefade.in{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidedown.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;-moz-transform:translateY(0);-moz-animation-name:slideinfromtop;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slidedown.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slidedown.out.reverse{-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-webkit-animation-name:slideouttotop;-moz-animation-name:slideouttotop;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfromtop{from{-webkit-transform:translateY(-100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfromtop{from{-moz-transform:translateY(-100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttotop{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(-100%)}}@-moz-keyframes slideouttotop{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(-100%)}}.slideup.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;-moz-transform:translateY(0);-moz-animation-name:slideinfrombottom;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slideup.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slideup.out.reverse{-webkit-transform:translateY(100%);-moz-transform:translateY(100%);-webkit-animation-name:slideouttobottom;-moz-animation-name:slideouttobottom;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfrombottom{from{-webkit-transform:translateY(100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfrombottom{from{-moz-transform:translateY(100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttobottom{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(100%)}}@-moz-keyframes slideouttobottom{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(100%)}}.viewport-flip{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.flip{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-moz-backface-visibility:hidden;-moz-transform:translateX(0)}.flip.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-webkit-animation-duration:175ms;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-moz-animation-duration:175ms}.flip.in{-webkit-animation-name:flipintoright;-webkit-animation-duration:225ms;-moz-animation-name:flipintoright;-moz-animation-duration:225ms}.flip.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.flip.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.viewport-turn{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.turn{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-webkit-transform-origin:0 0;-moz-backface-visibility:hidden;-moz-transform:translateX(0);-moz-transform-origin:0 0}.turn.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-webkit-animation-duration:125ms;-moz-animation-duration:125ms}.turn.in{-webkit-animation-name:flipintoright;-moz-animation-name:flipintoright;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.turn.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.turn.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.flow{-webkit-transform-origin:50% 30%;-moz-transform-origin:50% 30%;-webkit-box-shadow:0 0 20px rgba(0,0,0,.4);-moz-box-shadow:0 0 20px rgba(0,0,0,.4)}.ui-dialog.flow{-webkit-transform-origin:none;-moz-transform-origin:none;-webkit-box-shadow:none;-moz-box-shadow:none}.flow.out{-webkit-transform:translateX(-100%) scale(.7);-webkit-animation-name:flowouttoleft;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(-100%) scale(.7);-moz-animation-name:flowouttoleft;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.in{-webkit-transform:translateX(0) scale(1);-webkit-animation-name:flowinfromright;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(0) scale(1);-moz-animation-name:flowinfromright;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:flowouttoright;-moz-transform:translateX(100%);-moz-animation-name:flowouttoright}.flow.in.reverse{-webkit-animation-name:flowinfromleft;-moz-animation-name:flowinfromleft}@-webkit-keyframes flowouttoleft{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(-100%) scale(.7)}}@-moz-keyframes flowouttoleft{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(-100%) scale(.7)}}@-webkit-keyframes flowouttoright{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(100%) scale(.7)}}@-moz-keyframes flowouttoright{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(100%) scale(.7)}}@-webkit-keyframes flowinfromleft{0%{-webkit-transform:translateX(-100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromleft{0%{-moz-transform:translateX(-100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}@-webkit-keyframes flowinfromright{0%{-webkit-transform:translateX(100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromright{0%{-moz-transform:translateX(100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d{overflow:hidden}.ui-block-a,.ui-block-b,.ui-block-c,.ui-block-d,.ui-block-e{margin:0;padding:0;border:0;float:left;min-height:1px}.ui-grid-solo .ui-block-a{width:100%;float:none}.ui-grid-a .ui-block-a,.ui-grid-a .ui-block-b{width:50%}.ui-grid-a .ui-block-a{clear:left}.ui-grid-b .ui-block-a,.ui-grid-b .ui-block-b,.ui-grid-b .ui-block-c{width:33.333%}.ui-grid-b .ui-block-a{clear:left}.ui-grid-c .ui-block-a,.ui-grid-c .ui-block-b,.ui-grid-c .ui-block-c,.ui-grid-c .ui-block-d{width:25%}.ui-grid-c .ui-block-a{clear:left}.ui-grid-d .ui-block-a,.ui-grid-d .ui-block-b,.ui-grid-d .ui-block-c,.ui-grid-d .ui-block-d,.ui-grid-d .ui-block-e{width:20%}.ui-grid-d .ui-block-a{clear:left}.ui-header-fixed,.ui-footer-fixed{left:0;right:0;width:100%;position:fixed;z-index:1000}.ui-header-fixed{top:0}.ui-footer-fixed{bottom:0}.ui-header-fullscreen,.ui-footer-fullscreen{opacity:.9}.ui-page-header-fixed{padding-top:2.5em}.ui-page-footer-fixed{padding-bottom:3em}.ui-page-header-fullscreen .ui-content,.ui-page-footer-fullscreen .ui-content{padding:0}.ui-fixed-hidden{position:absolute}.ui-page-header-fullscreen .ui-fixed-hidden,.ui-page-footer-fullscreen .ui-fixed-hidden{left:-99999em}.ui-header-fixed .ui-btn,.ui-footer-fixed .ui-btn{z-index:10}.ui-navbar{overflow:hidden}.ui-navbar ul,.ui-navbar-expanded ul{list-style:none;padding:0;margin:0;position:relative;display:block;border:0}.ui-navbar-collapsed ul{float:left;width:75%;margin-right:-2px}.ui-navbar-collapsed .ui-navbar-toggle{float:left;width:25%}.ui-navbar li.ui-navbar-truncate{position:absolute;left:-9999px;top:-9999px}.ui-navbar li .ui-btn,.ui-navbar .ui-navbar-toggle .ui-btn{display:block;font-size:12px;text-align:center;margin:0;border-right-width:0;max-width:100%}.ui-navbar li .ui-btn{margin-right:-1px}.ui-navbar li .ui-btn:last-child{margin-right:0}.ui-header .ui-navbar li .ui-btn,.ui-header .ui-navbar .ui-navbar-toggle .ui-btn,.ui-footer .ui-navbar li .ui-btn,.ui-footer .ui-navbar .ui-navbar-toggle .ui-btn{border-top-width:0;border-bottom-width:0}.ui-navbar .ui-btn-inner{padding-left:2px;padding-right:2px}.ui-navbar-noicons li .ui-btn .ui-btn-inner,.ui-navbar-noicons .ui-navbar-toggle .ui-btn-inner{padding-top:.8em;padding-bottom:.9em}.ui-navbar-expanded .ui-btn{margin:0;font-size:14px}.ui-navbar-expanded .ui-btn-inner{padding-left:5px;padding-right:5px}.ui-navbar-expanded .ui-btn-icon-top .ui-btn-inner{padding:45px 5px 15px;text-align:center}.ui-navbar-expanded .ui-btn-icon-top .ui-icon{top:15px}.ui-navbar-expanded .ui-btn-icon-bottom .ui-btn-inner{padding:15px 5px 45px;text-align:center}.ui-navbar-expanded .ui-btn-icon-bottom .ui-icon{bottom:15px}.ui-navbar-expanded li .ui-btn .ui-btn-inner{min-height:2.5em}.ui-navbar-expanded .ui-navbar-noicons .ui-btn .ui-btn-inner{padding-top:1.8em;padding-bottom:1.9em}.ui-btn{display:block;text-align:center;cursor:pointer;position:relative;margin:.5em 5px;padding:0}.ui-mini{margin:.25em 5px}.ui-btn-inner{padding:.6em 20px;min-width:.75em;display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative;zoom:1}.ui-btn input,.ui-btn button{z-index:2}.ui-btn-left,.ui-btn-right,.ui-btn-inline{display:inline-block}.ui-btn-block{display:block}.ui-header .ui-btn,.ui-footer .ui-btn{display:inline-block;margin:0}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-mini .ui-btn-inner{font-size:12.5px;padding:.55em 11px .5em}.ui-header .ui-fullsize .ui-btn-inner,.ui-footer .ui-fullsize .ui-btn-inner{font-size:16px;padding:.6em 25px}.ui-btn-icon-notext{width:24px;height:24px}.ui-btn-icon-notext .ui-btn-inner{padding:0;height:100%}.ui-btn-icon-notext .ui-btn-inner .ui-icon{margin:2px 1px 2px 3px}.ui-btn-text{position:relative;z-index:1;width:100%}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-9999px}.ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-btn-icon-right .ui-btn-inner{padding-right:40px}.ui-btn-icon-top .ui-btn-inner{padding-top:40px}.ui-btn-icon-bottom .ui-btn-inner{padding-bottom:40px}.ui-header .ui-btn-icon-left .ui-btn-inner,.ui-footer .ui-btn-icon-left .ui-btn-inner,.ui-mini .ui-btn-icon-left .ui-btn-inner{padding-left:30px}.ui-header .ui-btn-icon-right .ui-btn-inner,.ui-footer .ui-btn-icon-right .ui-btn-inner,.ui-mini .ui-btn-icon-right .ui-btn-inner{padding-right:30px}.ui-header .ui-btn-icon-top .ui-btn-inner,.ui-footer .ui-btn-icon-top .ui-btn-inner,.ui-mini .ui-btn-icon-top .ui-btn-inner{padding:30px 3px .5em 3px}.ui-header .ui-btn-icon-bottom .ui-btn-inner,.ui-footer .ui-btn-icon-bottom .ui-btn-inner,.ui-mini .ui-btn-icon-bottom .ui-btn-inner{padding:.55em 3px 30px 3px}.ui-btn-icon-notext .ui-icon{display:block;z-index:0}.ui-btn-icon-left .ui-btn-inner .ui-icon,.ui-btn-icon-right .ui-btn-inner .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-btn-icon-top .ui-btn-inner .ui-icon,.ui-btn-icon-bottom .ui-btn-inner .ui-icon{position:absolute;left:50%;margin-left:-9px}.ui-btn-icon-left .ui-icon{left:10px}.ui-btn-icon-right .ui-icon{right:10px}.ui-btn-icon-top .ui-icon{top:10px}.ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-header .ui-btn-icon-left .ui-icon,.ui-footer .ui-btn-icon-left .ui-icon,.ui-mini.ui-btn-icon-left .ui-icon,.ui-mini .ui-btn-icon-left .ui-icon{left:5px}.ui-header .ui-btn-icon-right .ui-icon,.ui-footer .ui-btn-icon-right .ui-icon,.ui-mini.ui-btn-icon-right .ui-icon,.ui-mini .ui-btn-icon-right .ui-icon{right:5px}.ui-header .ui-btn-icon-top .ui-icon,.ui-footer .ui-btn-icon-top .ui-icon,.ui-mini.ui-btn-icon-top .ui-icon,.ui-mini .ui-btn-icon-top .ui-icon{top:5px}.ui-header .ui-btn-icon-bottom .ui-icon,.ui-footer .ui-btn-icon-bottom .ui-icon,.ui-mini.ui-btn-icon-bottom .ui-icon,.ui-mini .ui-btn-icon-bottom .ui-icon{bottom:5px}.ui-btn-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:button;opacity:.1;cursor:pointer;background:#fff;background:rgba(255,255,255,0);filter:Alpha(Opacity=.0001);font-size:1px;border:0;text-indent:-9999px}.ui-collapsible{margin:.5em 0}.ui-collapsible-heading{font-size:16px;display:block;margin:0 -8px;padding:0;border-width:0 0 1px 0;position:relative}.ui-collapsible-heading a{text-align:left;margin:0}.ui-collapsible-heading .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-collapsible-heading .ui-btn-icon-right .ui-btn-inner{padding-left:12px;padding-right:40px}.ui-collapsible-heading .ui-btn-icon-top .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-bottom .ui-btn-inner{padding-right:40px;text-align:center}.ui-collapsible-heading a span.ui-btn{position:absolute;left:6px;top:50%;margin:-12px 0 0 0;width:20px;height:20px;padding:1px 0 1px 2px;text-indent:-9999px}.ui-collapsible-heading a span.ui-btn .ui-btn-inner{padding:10px 0}.ui-collapsible-heading a span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:absolute;top:-9999px;left:0}.ui-collapsible-content{display:block;margin:0 -8px;padding:10px 16px;border-top:0;background-image:none;font-weight:normal}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible{margin:-1px 0 0}.ui-controlgroup,fieldset.ui-controlgroup{padding:0;margin:0 0 .5em;zoom:1}.ui-bar .ui-controlgroup{margin:0 .3em}.ui-controlgroup-label{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .4em}.ui-controlgroup-controls{display:block;width:100%}.ui-controlgroup li{list-style:none}.ui-controlgroup-vertical .ui-btn,.ui-controlgroup-vertical .ui-checkbox,.ui-controlgroup-vertical .ui-radio{margin:0;border-bottom-width:0}.ui-controlgroup-controls label.ui-select{position:absolute;left:-9999px}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-horizontal{padding:0}.ui-controlgroup-horizontal .ui-btn-inner{text-align:center}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-select{display:inline-block;margin:0 -6px 0 0}.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{float:left;clear:none;margin:0 -1px 0 0}.ui-controlgroup-horizontal .ui-checkbox .ui-btn,.ui-controlgroup-horizontal .ui-radio .ui-btn,.ui-controlgroup-horizontal .ui-checkbox:last-child,.ui-controlgroup-horizontal .ui-radio:last-child{margin-right:0}.ui-controlgroup-horizontal .ui-controlgroup-last{margin-right:0}.ui-controlgroup .ui-checkbox label,.ui-controlgroup .ui-radio label{font-size:16px}@media all and (min-width:450px){.ui-field-contain .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-controlgroup-controls{width:60%;display:inline-block}.ui-field-contain .ui-controlgroup .ui-select{width:100%}.ui-field-contain .ui-controlgroup-horizontal .ui-select{width:auto}}.ui-dialog{background:none!important}.ui-dialog-contain{width:92.5%;max-width:500px;margin:10% auto 15px auto;padding:0}.ui-dialog .ui-header{margin-top:15%;border:0;overflow:hidden}.ui-dialog .ui-header,.ui-dialog .ui-content,.ui-dialog .ui-footer{display:block;position:relative;width:auto}.ui-dialog .ui-header,.ui-dialog .ui-footer{z-index:10;padding:0}.ui-dialog .ui-footer{padding:0 15px}.ui-dialog .ui-content{padding:15px}.ui-dialog{margin-top:-15px}.ui-checkbox,.ui-radio{position:relative;clear:both;margin:.2em 0 .5em;z-index:1}.ui-checkbox .ui-btn,.ui-radio .ui-btn{margin:0;text-align:left;z-index:2}.ui-checkbox .ui-btn-inner,.ui-radio .ui-btn-inner{white-space:normal}.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner{padding-left:45px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-left .ui-btn-inner{padding-left:36px}.ui-checkbox .ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-right .ui-btn-inner{padding-right:36px}.ui-checkbox .ui-btn-icon-top .ui-btn-inner,.ui-radio .ui-btn-icon-top .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-btn-icon-bottom .ui-btn-inner,.ui-radio .ui-btn-icon-bottom .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-icon,.ui-radio .ui-icon{top:1.1em}.ui-checkbox .ui-btn-icon-left .ui-icon,.ui-radio .ui-btn-icon-left .ui-icon{left:15px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-icon,.ui-radio .ui-mini.ui-btn-icon-left .ui-icon{left:9px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox .ui-btn-icon-top .ui-icon,.ui-radio .ui-btn-icon-top .ui-icon{top:10px}.ui-checkbox .ui-btn-icon-bottom .ui-icon,.ui-radio .ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox input,.ui-radio input{position:absolute;left:20px;top:50%;width:10px;height:10px;margin:-5px 0 0 0;outline:0!important;z-index:1}.ui-field-contain,fieldset.ui-field-contain{padding:.8em 0;margin:0;border-width:0 0 1px 0;overflow:visible}.ui-field-contain:first-child{border-top-width:0}.ui-header .ui-field-contain-left,.ui-header .ui-field-contain-right{position:absolute;top:0;width:25%}.ui-header .ui-field-contain-left{left:1em}.ui-header .ui-field-contain-right{right:1em}@media all and (min-width:450px){.ui-field-contain,.ui-mobile fieldset.ui-field-contain{border-width:0;padding:0;margin:1em 0}}.ui-select{display:block;position:relative}.ui-select select{position:absolute;left:-9999px;top:-9999px}.ui-select .ui-btn{overflow:hidden;opacity:1;margin:0}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:button;left:0;top:0;width:100%;min-height:1.5em;min-height:100%;height:3em;max-height:100%;opacity:0;-ms-filter:"alpha(opacity=0)";filter:alpha(opacity=0);z-index:2}.ui-select .ui-disabled{opacity:.3}@-moz-document url-prefix(){.ui-select .ui-btn select{opacity:.0001}}.ui-select .ui-btn select.ui-select-nativeonly{opacity:1;text-indent:0}.ui-select .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-select .ui-btn-icon-right .ui-icon{right:15px}.ui-select .ui-mini.ui-btn-icon-right .ui-icon{right:7px}label.ui-select{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}.ui-select .ui-btn-text,.ui-selectmenu .ui-btn-text{display:block;min-height:1em;overflow:hidden!important}.ui-select .ui-btn-text{text-overflow:ellipsis}.ui-selectmenu{position:absolute;padding:0;z-index:1100!important;width:80%;max-width:350px;padding:6px}.ui-selectmenu .ui-listview{margin:0}.ui-selectmenu .ui-btn.ui-li-divider{cursor:default}.ui-selectmenu-hidden{top:-9999px;left:-9999px}.ui-selectmenu-screen{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99}.ui-screen-hidden,.ui-selectmenu-list .ui-li .ui-icon{display:none}.ui-selectmenu-list .ui-li .ui-icon{display:block}.ui-li.ui-selectmenu-placeholder{display:none}.ui-selectmenu .ui-header .ui-title{margin:.6em 46px .8em}@media all and (min-width:450px){.ui-field-contain label.ui-select{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-select{width:60%;display:inline-block}}.ui-selectmenu .ui-header h1:after{content:'.';visibility:hidden}label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}input.ui-input-text,textarea.ui-input-text{background-image:none;padding:.4em;line-height:1.4;font-size:16px;display:block;width:97%;outline:0}.ui-header input.ui-input-text,.ui-footer input.ui-input-text{margin-left:1.25%;padding:.4em 1%;width:95.5%}input.ui-input-text{-webkit-appearance:none}textarea.ui-input-text{height:50px;-webkit-transition:height 200ms linear;-moz-transition:height 200ms linear;-o-transition:height 200ms linear;transition:height 200ms linear}.ui-input-search{padding:0 30px;background-image:none;position:relative}.ui-icon-searchfield:after{position:absolute;left:7px;top:50%;margin-top:-9px;content:"";width:18px;height:18px;opacity:.5}.ui-input-search input.ui-input-text{border:0;width:98%;padding:.4em 0;margin:0;display:block;background:transparent none;outline:0!important}.ui-input-search .ui-input-clear{position:absolute;right:0;top:50%;margin-top:-13px}.ui-mini .ui-input-clear{right:-3px}.ui-input-search .ui-input-clear-hidden{display:none}input.ui-mini,.ui-mini input,textarea.ui-mini{font-size:14px}textarea.ui-mini{height:45px}@media all and (min-width:450px){.ui-field-contain label.ui-input-text{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text,.ui-field-contain .ui-input-search{width:60%;display:inline-block}.ui-field-contain .ui-input-search{width:50%}.ui-hide-label input.ui-input-text,.ui-hide-label textarea.ui-input-text,.ui-hide-label .ui-input-search{padding:.4em;width:97%}.ui-input-search input.ui-input-text{width:98%}}.ui-listview{margin:0;counter-reset:listnumbering}.ui-content .ui-listview{margin:-15px}.ui-content .ui-listview-inset{margin:1em 0}.ui-listview,.ui-li{list-style:none;padding:0}.ui-li,.ui-li.ui-field-contain{display:block;margin:0;position:relative;overflow:visible;text-align:left;border-width:0;border-top-width:1px}.ui-li .ui-btn-text a.ui-link-inherit{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-divider,.ui-li-static{padding:.5em 15px;font-size:14px;font-weight:bold}.ui-li-divider{counter-reset:listnumbering}ol.ui-listview .ui-link-inherit:before,ol.ui-listview .ui-li-static:before,.ui-li-dec{font-size:.8em;display:inline-block;padding-right:.3em;font-weight:normal;counter-increment:listnumbering;content:counter(listnumbering) ". "}ol.ui-listview .ui-li-jsnumbering:before{content:""!important}.ui-listview-inset .ui-li{border-right-width:1px;border-left-width:1px}.ui-li:last-child,.ui-li.ui-field-contain:last-child{border-bottom-width:1px}.ui-li>.ui-btn-inner{display:block;position:relative;padding:0}.ui-li .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li{padding:.7em 15px .7em 15px;display:block}.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-thumb{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-icon{min-height:20px;padding-left:40px}.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-count{padding-right:45px}.ui-li-has-arrow .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow{padding-right:30px}.ui-li-has-arrow.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow.ui-li-has-count{padding-right:75px}.ui-li-has-count .ui-btn-text{padding-right:15px}.ui-li-heading{font-size:16px;font-weight:bold;display:block;margin:.6em 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-desc{font-size:12px;font-weight:normal;display:block;margin:-.5em 0 .6em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-thumb,.ui-listview .ui-li-icon{position:absolute;left:1px;top:0;max-height:80px;max-width:80px}.ui-listview .ui-li-icon{max-height:40px;max-width:40px;left:10px;top:.9em}.ui-li-thumb,.ui-listview .ui-li-icon,.ui-li-content{float:left;margin-right:10px}.ui-li-aside{float:right;width:50%;text-align:right;margin:.3em 0}@media all and (min-width:480px){.ui-li-aside{width:45%}}.ui-li-divider{cursor:default}.ui-li-has-alt .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt{padding-right:95px}.ui-li-has-count .ui-li-count{position:absolute;font-size:11px;font-weight:bold;padding:.2em .5em;top:50%;margin-top:-.9em;right:48px}.ui-li-divider .ui-li-count,.ui-li-static .ui-li-count{right:10px}.ui-li-has-alt .ui-li-count{right:55px}.ui-li-link-alt{position:absolute;width:40px;height:100%;border-width:0;border-left-width:1px;top:0;right:0;margin:0;padding:0;z-index:2}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-11px 0 0 0;border-bottom-width:1px;z-index:-1}.ui-li-link-alt .ui-btn-inner{padding:0;height:100%;position:absolute;width:100%;top:0;left:0}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-listview * .ui-btn-inner>.ui-btn>.ui-btn-inner{border-top:0}.ui-listview-filter{border-width:0;overflow:hidden;margin:-15px -15px 15px -15px}.ui-listview-filter .ui-input-search{margin:5px;width:auto;display:block}.ui-listview-filter-inset{margin:-15px -5px -15px -5px;background:transparent}.ui-li.ui-screen-hidden{display:none}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}input.ui-slider-input,.ui-field-contain input.ui-slider-input{display:inline-block;width:50px}select.ui-slider-switch{display:none}div.ui-slider{position:relative;display:inline-block;overflow:visible;height:15px;padding:0;margin:0 2% 0 20px;top:4px;width:65%}div.ui-slider-mini{height:12px;margin-left:10px}div.ui-slider-bg{border:0;height:100%;padding-right:8px}.ui-controlgroup a.ui-slider-handle,a.ui-slider-handle{position:absolute;z-index:1;top:50%;width:28px;height:28px;margin-top:-15px;margin-left:-15px;outline:0}a.ui-slider-handle .ui-btn-inner{padding:0;height:100%}div.ui-slider-mini a.ui-slider-handle{height:14px;width:14px;margin:-8px 0 0 -7px}div.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:-9px 0 0 -9px}@media all and (min-width:450px){.ui-field-contain label.ui-slider{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain div.ui-slider{width:43%}.ui-field-contain div.ui-slider-switch{width:5.5em}}div.ui-slider-switch{height:32px;margin-left:0;width:5.8em}a.ui-slider-handle-snapping{-webkit-transition:left 70ms linear;-moz-transition:left 70ms linear}div.ui-slider-switch .ui-slider-handle{margin-top:1px}.ui-slider-inneroffset{margin:0 16px;position:relative;z-index:1}div.ui-slider-switch.ui-slider-mini{width:5em;height:29px}div.ui-slider-switch.ui-slider-mini .ui-slider-inneroffset{margin:0 15px 0 14px}div.ui-slider-switch.ui-slider-mini .ui-slider-handle{width:25px;height:25px;margin:1px 0 0 -13px}div.ui-slider-switch.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:0}span.ui-slider-label{position:absolute;text-align:center;width:100%;overflow:hidden;font-size:16px;top:0;line-height:2;min-height:100%;border-width:0;white-space:nowrap}.ui-slider-mini span.ui-slider-label{font-size:14px}span.ui-slider-label-a{z-index:1;left:0;text-indent:-1.5em}span.ui-slider-label-b{z-index:0;right:0;text-indent:1.5em}.ui-slider-inline{width:120px;display:inline-block} \ No newline at end of file diff -r 6c6239aec462 -r d25f4c0fa160 OrthancExplorer/libs/jquery.mobile.min.js --- a/OrthancExplorer/libs/jquery.mobile.min.js Wed Jun 10 18:49:21 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,177 +0,0 @@ -/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ -(function(D,s,k){typeof define==="function"&&define.amd?define(["jquery"],function(a){k(a,D,s);return a.mobile}):k(D.jQuery,D,s)})(this,document,function(D,s,k){(function(a,c,b,e){function f(a){for(;a&&typeof a.originalEvent!=="undefined";)a=a.originalEvent;return a}function d(b){for(var d={},f,g;b;){f=a.data(b,t);for(g in f)if(f[g])d[g]=d.hasVirtualBinding=true;b=b.parentNode}return d}function g(){y&&(clearTimeout(y),y=0);y=setTimeout(function(){F=y=0;C.length=0;z=false;G=true},a.vmouse.resetTimerDuration)} -function h(b,d,g){var c,h;if(!(h=g&&g[b])){if(g=!g)a:{for(g=d.target;g;){if((h=a.data(g,t))&&(!b||h[b]))break a;g=g.parentNode}g=null}h=g}if(h){c=d;var g=c.type,z,j;c=a.Event(c);c.type=b;h=c.originalEvent;z=a.event.props;g.search(/^(mouse|click)/)>-1&&(z=w);if(h)for(j=z.length;j;)b=z[--j],c[b]=h[b];if(g.search(/mouse(down|up)|click/)>-1&&!c.which)c.which=1;if(g.search(/^touch/)!==-1&&(b=f(h),g=b.touches,b=b.changedTouches,g=g&&g.length?g[0]:b&&b.length?b[0]:e))for(h=0,len=u.length;hz||Math.abs(c.pageY-E)>z;flags=d(b.target);A&&!e&&h("vmousecancel",b,flags);h("vmousemove",b,flags);g()}}function l(a){if(!G){G=true;var b=d(a.target),c;h("vmouseup",a,b);if(!A&&(c=h("vclick",a,b))&&c.isDefaultPrevented())c=f(a).changedTouches[0],C.push({touchID:F,x:c.clientX,y:c.clientY}),z=true;h("vmouseout",a,b);A=false;g()}}function r(b){var b= -a.data(b,t),d;if(b)for(d in b)if(b[d])return true;return false}function n(){}function k(b){var d=b.substr(1);return{setup:function(){r(this)||a.data(this,t,{});a.data(this,t)[b]=true;v[b]=(v[b]||0)+1;v[b]===1&&H.bind(d,j);a(this).bind(d,n);if(K)v.touchstart=(v.touchstart||0)+1,v.touchstart===1&&H.bind("touchstart",o).bind("touchend",l).bind("touchmove",p).bind("scroll",m)},teardown:function(){--v[b];v[b]||H.unbind(d,j);K&&(--v.touchstart,v.touchstart||H.unbind("touchstart",o).unbind("touchmove",p).unbind("touchend", -l).unbind("scroll",m));var f=a(this),g=a.data(this,t);g&&(g[b]=false);f.unbind(d,n);r(this)||f.removeData(t)}}}var t="virtualMouseBindings",x="virtualTouchID",c="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),u="clientX clientY pageX pageY screenX screenY".split(" "),w=a.event.props.concat(a.event.mouseHooks?a.event.mouseHooks.props:[]),v={},y=0,s=0,E=0,A=false,C=[],z=false,G=false,K="addEventListener"in b,H=a(b),L=1,F=0;a.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10, -resetTimerDuration:1500};for(var I=0;I7);a.fn[f]=function(a){return a?this.bind(f,a):this.trigger(f)};a.fn[f].delay=50;h[f]=a.extend(h[f],{setup:function(){if(o)return false;a(g.start)},teardown:function(){if(o)return false;a(g.stop)}});g=function(){function g(){var b=e(),d=t(r);if(b!==r)k(r=b,d),a(c).trigger(f);else if(d!==r)location.href=location.href.replace(/#.*/,"")+d;j=setTimeout(g,a.fn[f].delay)}var h={},j,r=e(),n=function(a){return a},k= -n,t=n;h.start=function(){j||g()};h.stop=function(){j&&clearTimeout(j);j=b};a.browser.msie&&!o&&function(){var b,c;h.start=function(){if(!b)c=(c=a.fn[f].src)&&c+e(),b=a('' + ).bind('load', function () { + $.each(names, function (i, name) { + message[name] = options[name]; + }); + message.dataType = message.dataType.replace('postmessage ', ''); + $(window).bind(eventName, function (e) { + e = e.originalEvent; + var data = e.data, + ev; + if (e.origin === target && data.id === message.id) { + if (data.type === 'progress') { + ev = document.createEvent('Event'); + ev.initEvent(data.type, false, true); + $.extend(ev, data); + xhrUpload.dispatchEvent(ev); + } else { + completeCallback( + data.status, + data.statusText, + {postmessage: data.result}, + data.headers + ); + iframe.remove(); + $(window).unbind(eventName); + } + } + }); + iframe[0].contentWindow.postMessage( + message, + target + ); + }).appendTo(document.body); + }, + abort: function () { + if (iframe) { + iframe.remove(); + } + } + }; + } + }); + +})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/cors/jquery.xdr-transport.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/cors/jquery.xdr-transport.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,85 @@ +/* + * jQuery XDomainRequest Transport Plugin 1.1.2 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + * + * Based on Julian Aubourg's ajaxHooks xdr.js: + * https://github.com/jaubourg/ajaxHooks/ + */ + +/*jslint unparam: true */ +/*global define, window, XDomainRequest */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + if (window.XDomainRequest && !$.support.cors) { + $.ajaxTransport(function (s) { + if (s.crossDomain && s.async) { + if (s.timeout) { + s.xdrTimeout = s.timeout; + delete s.timeout; + } + var xdr; + return { + send: function (headers, completeCallback) { + function callback(status, statusText, responses, responseHeaders) { + xdr.onload = xdr.onerror = xdr.ontimeout = $.noop; + xdr = null; + completeCallback(status, statusText, responses, responseHeaders); + } + xdr = new XDomainRequest(); + // XDomainRequest only supports GET and POST: + if (s.type === 'DELETE') { + s.url = s.url + (/\?/.test(s.url) ? '&' : '?') + + '_method=DELETE'; + s.type = 'POST'; + } else if (s.type === 'PUT') { + s.url = s.url + (/\?/.test(s.url) ? '&' : '?') + + '_method=PUT'; + s.type = 'POST'; + } + xdr.open(s.type, s.url); + xdr.onload = function () { + callback( + 200, + 'OK', + {text: xdr.responseText}, + 'Content-Type: ' + xdr.contentType + ); + }; + xdr.onerror = function () { + callback(404, 'Not Found'); + }; + if (s.xdrTimeout) { + xdr.ontimeout = function () { + callback(0, 'timeout'); + }; + xdr.timeout = s.xdrTimeout; + } + xdr.send((s.hasContent && s.data) || null); + }, + abort: function () { + if (xdr) { + xdr.onerror = $.noop(); + xdr.abort(); + } + } + }; + } + }); + } +})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload-fp.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload-fp.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,219 @@ +/* + * jQuery File Upload File Processing Plugin 1.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'load-image', + 'canvas-to-blob', + './jquery.fileupload' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery, + window.loadImage + ); + } +}(function ($, loadImage) { + 'use strict'; + + // The File Upload IP version extends the basic fileupload widget + // with file processing functionality: + $.widget('blueimpFP.fileupload', $.blueimp.fileupload, { + + options: { + // The list of file processing actions: + process: [ + /* + { + action: 'load', + fileTypes: /^image\/(gif|jpeg|png)$/, + maxFileSize: 20000000 // 20MB + }, + { + action: 'resize', + maxWidth: 1920, + maxHeight: 1200, + minWidth: 800, + minHeight: 600 + }, + { + action: 'save' + } + */ + ], + + // The add callback is invoked as soon as files are added to the + // fileupload widget (via file input selection, drag & drop or add + // API call). See the basic file upload widget for more information: + add: function (e, data) { + $(this).fileupload('process', data).done(function () { + data.submit(); + }); + } + }, + + processActions: { + // Loads the image given via data.files and data.index + // as canvas element. + // Accepts the options fileTypes (regular expression) + // and maxFileSize (integer) to limit the files to load: + load: function (data, options) { + var that = this, + file = data.files[data.index], + dfd = $.Deferred(); + if (window.HTMLCanvasElement && + window.HTMLCanvasElement.prototype.toBlob && + ($.type(options.maxFileSize) !== 'number' || + file.size < options.maxFileSize) && + (!options.fileTypes || + options.fileTypes.test(file.type))) { + loadImage( + file, + function (canvas) { + data.canvas = canvas; + dfd.resolveWith(that, [data]); + }, + {canvas: true} + ); + } else { + dfd.rejectWith(that, [data]); + } + return dfd.promise(); + }, + // Resizes the image given as data.canvas and updates + // data.canvas with the resized image. + // Accepts the options maxWidth, maxHeight, minWidth and + // minHeight to scale the given image: + resize: function (data, options) { + if (data.canvas) { + var canvas = loadImage.scale(data.canvas, options); + if (canvas.width !== data.canvas.width || + canvas.height !== data.canvas.height) { + data.canvas = canvas; + data.processed = true; + } + } + return data; + }, + // Saves the processed image given as data.canvas + // inplace at data.index of data.files: + save: function (data, options) { + // Do nothing if no processing has happened: + if (!data.canvas || !data.processed) { + return data; + } + var that = this, + file = data.files[data.index], + name = file.name, + dfd = $.Deferred(), + callback = function (blob) { + if (!blob.name) { + if (file.type === blob.type) { + blob.name = file.name; + } else if (file.name) { + blob.name = file.name.replace( + /\..+$/, + '.' + blob.type.substr(6) + ); + } + } + // Store the created blob at the position + // of the original file in the files list: + data.files[data.index] = blob; + dfd.resolveWith(that, [data]); + }; + // Use canvas.mozGetAsFile directly, to retain the filename, as + // Gecko doesn't support the filename option for FormData.append: + if (data.canvas.mozGetAsFile) { + callback(data.canvas.mozGetAsFile( + (/^image\/(jpeg|png)$/.test(file.type) && name) || + ((name && name.replace(/\..+$/, '')) || + 'blob') + '.png', + file.type + )); + } else { + data.canvas.toBlob(callback, file.type); + } + return dfd.promise(); + } + }, + + // Resizes the file at the given index and stores the created blob at + // the original position of the files list, returns a Promise object: + _processFile: function (files, index, options) { + var that = this, + dfd = $.Deferred().resolveWith(that, [{ + files: files, + index: index + }]), + chain = dfd.promise(); + that._processing += 1; + $.each(options.process, function (i, settings) { + chain = chain.pipe(function (data) { + return that.processActions[settings.action] + .call(this, data, settings); + }); + }); + chain.always(function () { + that._processing -= 1; + if (that._processing === 0) { + that.element + .removeClass('fileupload-processing'); + } + }); + if (that._processing === 1) { + that.element.addClass('fileupload-processing'); + } + return chain; + }, + + // Processes the files given as files property of the data parameter, + // returns a Promise object that allows one to bind a done handler, which + // will be invoked after processing all files (inplace) is done: + process: function (data) { + var that = this, + options = $.extend({}, this.options, data); + if (options.process && options.process.length && + this._isXHRUpload(options)) { + $.each(data.files, function (index, file) { + that._processingQueue = that._processingQueue.pipe( + function () { + var dfd = $.Deferred(); + that._processFile(data.files, index, options) + .always(function () { + dfd.resolveWith(that); + }); + return dfd.promise(); + } + ); + }); + } + return this._processingQueue; + }, + + _create: function () { + $.blueimp.fileupload.prototype._create.call(this); + this._processing = 0; + this._processingQueue = $.Deferred().resolveWith(this) + .promise(); + } + + }); + +})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload-ui.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload-ui.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,702 @@ +/* + * jQuery File Upload User Interface Plugin 6.9.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, URL, webkitURL, FileReader */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'tmpl', + 'load-image', + './jquery.fileupload-fp' + ], factory); + } else { + // Browser globals: + factory( + window.jQuery, + window.tmpl, + window.loadImage + ); + } +}(function ($, tmpl, loadImage) { + 'use strict'; + + // The UI version extends the FP (file processing) version or the basic + // file upload widget and adds complete user interface interaction: + var parentWidget = ($.blueimpFP || $.blueimp).fileupload; + $.widget('blueimpUI.fileupload', parentWidget, { + + options: { + // By default, files added to the widget are uploaded as soon + // as the user clicks on the start buttons. To enable automatic + // uploads, set the following option to true: + autoUpload: false, + // The following option limits the number of files that are + // allowed to be uploaded using this widget: + maxNumberOfFiles: undefined, + // The maximum allowed file size: + maxFileSize: undefined, + // The minimum allowed file size: + minFileSize: undefined, + // The regular expression for allowed file types, matches + // against either file type or file name: + acceptFileTypes: /.+$/i, + // The regular expression to define for which files a preview + // image is shown, matched against the file type: + previewSourceFileTypes: /^image\/(gif|jpeg|png)$/, + // The maximum file size of images that are to be displayed as preview: + previewSourceMaxFileSize: 5000000, // 5MB + // The maximum width of the preview images: + previewMaxWidth: 80, + // The maximum height of the preview images: + previewMaxHeight: 80, + // By default, preview images are displayed as canvas elements + // if supported by the browser. Set the following option to false + // to always display preview images as img elements: + previewAsCanvas: true, + // The ID of the upload template: + uploadTemplateId: 'template-upload', + // The ID of the download template: + downloadTemplateId: 'template-download', + // The container for the list of files. If undefined, it is set to + // an element with class "files" inside of the widget element: + filesContainer: undefined, + // By default, files are appended to the files container. + // Set the following option to true, to prepend files instead: + prependFiles: false, + // The expected data type of the upload response, sets the dataType + // option of the $.ajax upload requests: + dataType: 'json', + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop or add API call). + // See the basic file upload widget for more information: + add: function (e, data) { + var that = $(this).data('fileupload'), + options = that.options, + files = data.files; + $(this).fileupload('process', data).done(function () { + that._adjustMaxNumberOfFiles(-files.length); + data.isAdjusted = true; + data.files.valid = data.isValidated = that._validate(files); + data.context = that._renderUpload(files).data('data', data); + options.filesContainer[ + options.prependFiles ? 'prepend' : 'append' + ](data.context); + that._renderPreviews(files, data.context); + that._forceReflow(data.context); + that._transition(data.context).done( + function () { + if ((that._trigger('added', e, data) !== false) && + (options.autoUpload || data.autoUpload) && + data.autoUpload !== false && data.isValidated) { + data.submit(); + } + } + ); + }); + }, + // Callback for the start of each file upload request: + send: function (e, data) { + var that = $(this).data('fileupload'); + if (!data.isValidated) { + if (!data.isAdjusted) { + that._adjustMaxNumberOfFiles(-data.files.length); + } + if (!that._validate(data.files)) { + return false; + } + } + if (data.context && data.dataType && + data.dataType.substr(0, 6) === 'iframe') { + // Iframe Transport does not support progress events. + // In lack of an indeterminate progress bar, we set + // the progress to 100%, showing the full animated bar: + data.context + .find('.progress').addClass( + !$.support.transition && 'progress-animated' + ) + .attr('aria-valuenow', 100) + .find('.bar').css( + 'width', + '100%' + ); + } + return that._trigger('sent', e, data); + }, + // Callback for successful uploads: + done: function (e, data) { + var that = $(this).data('fileupload'), + template; + if (data.context) { + data.context.each(function (index) { + var file = ($.isArray(data.result) && + data.result[index]) || {error: 'emptyResult'}; + if (file.error) { + that._adjustMaxNumberOfFiles(1); + } + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .css('height', node.height()) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + } + ); + } + ); + }); + } else { + template = that._renderDownload(data.result) + .appendTo(that.options.filesContainer); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('completed', e, data); + } + ); + } + }, + // Callback for failed (abort or error) uploads: + fail: function (e, data) { + var that = $(this).data('fileupload'), + template; + that._adjustMaxNumberOfFiles(data.files.length); + if (data.context) { + data.context.each(function (index) { + if (data.errorThrown !== 'abort') { + var file = data.files[index]; + file.error = file.error || data.errorThrown || + true; + that._transition($(this)).done( + function () { + var node = $(this); + template = that._renderDownload([file]) + .replaceAll(node); + that._forceReflow(template); + that._transition(template).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + } + ); + } + ); + } else { + that._transition($(this)).done( + function () { + $(this).remove(); + that._trigger('failed', e, data); + } + ); + } + }); + } else if (data.errorThrown !== 'abort') { + that._adjustMaxNumberOfFiles(-data.files.length); + data.context = that._renderUpload(data.files) + .appendTo(that.options.filesContainer) + .data('data', data); + that._forceReflow(data.context); + that._transition(data.context).done( + function () { + data.context = $(this); + that._trigger('failed', e, data); + } + ); + } else { + that._trigger('failed', e, data); + } + }, + // Callback for upload progress events: + progress: function (e, data) { + if (data.context) { + var progress = parseInt(data.loaded / data.total * 100, 10); + data.context.find('.progress') + .attr('aria-valuenow', progress) + .find('.bar').css( + 'width', + progress + '%' + ); + } + }, + // Callback for global upload progress events: + progressall: function (e, data) { + var $this = $(this), + progress = parseInt(data.loaded / data.total * 100, 10), + globalProgressNode = $this.find('.fileupload-progress'), + extendedProgressNode = globalProgressNode + .find('.progress-extended'); + if (extendedProgressNode.length) { + extendedProgressNode.html( + $this.data('fileupload')._renderExtendedProgress(data) + ); + } + globalProgressNode + .find('.progress') + .attr('aria-valuenow', progress) + .find('.bar').css( + 'width', + progress + '%' + ); + }, + // Callback for uploads start, equivalent to the global ajaxStart event: + start: function (e) { + var that = $(this).data('fileupload'); + that._transition($(this).find('.fileupload-progress')).done( + function () { + that._trigger('started', e); + } + ); + }, + // Callback for uploads stop, equivalent to the global ajaxStop event: + stop: function (e) { + var that = $(this).data('fileupload'); + that._transition($(this).find('.fileupload-progress')).done( + function () { + $(this).find('.progress') + .attr('aria-valuenow', '0') + .find('.bar').css('width', '0%'); + $(this).find('.progress-extended').html(' '); + that._trigger('stopped', e); + } + ); + }, + // Callback for file deletion: + destroy: function (e, data) { + var that = $(this).data('fileupload'); + if (data.url) { + $.ajax(data); + that._adjustMaxNumberOfFiles(1); + } + that._transition(data.context).done( + function () { + $(this).remove(); + that._trigger('destroyed', e, data); + } + ); + } + }, + + // Link handler, that allows one to download files + // by drag & drop of the links to the desktop: + _enableDragToDesktop: function () { + var link = $(this), + url = link.prop('href'), + name = link.prop('download'), + type = 'application/octet-stream'; + link.bind('dragstart', function (e) { + try { + e.originalEvent.dataTransfer.setData( + 'DownloadURL', + [type, name, url].join(':') + ); + } catch (err) {} + }); + }, + + _adjustMaxNumberOfFiles: function (operand) { + if (typeof this.options.maxNumberOfFiles === 'number') { + this.options.maxNumberOfFiles += operand; + if (this.options.maxNumberOfFiles < 1) { + this._disableFileInputButton(); + } else { + this._enableFileInputButton(); + } + } + }, + + _formatFileSize: function (bytes) { + if (typeof bytes !== 'number') { + return ''; + } + if (bytes >= 1000000000) { + return (bytes / 1000000000).toFixed(2) + ' GB'; + } + if (bytes >= 1000000) { + return (bytes / 1000000).toFixed(2) + ' MB'; + } + return (bytes / 1000).toFixed(2) + ' KB'; + }, + + _formatBitrate: function (bits) { + if (typeof bits !== 'number') { + return ''; + } + if (bits >= 1000000000) { + return (bits / 1000000000).toFixed(2) + ' Gbit/s'; + } + if (bits >= 1000000) { + return (bits / 1000000).toFixed(2) + ' Mbit/s'; + } + if (bits >= 1000) { + return (bits / 1000).toFixed(2) + ' kbit/s'; + } + return bits + ' bit/s'; + }, + + _formatTime: function (seconds) { + var date = new Date(seconds * 1000), + days = parseInt(seconds / 86400, 10); + days = days ? days + 'd ' : ''; + return days + + ('0' + date.getUTCHours()).slice(-2) + ':' + + ('0' + date.getUTCMinutes()).slice(-2) + ':' + + ('0' + date.getUTCSeconds()).slice(-2); + }, + + _formatPercentage: function (floatValue) { + return (floatValue * 100).toFixed(2) + ' %'; + }, + + _renderExtendedProgress: function (data) { + return this._formatBitrate(data.bitrate) + ' | ' + + this._formatTime( + (data.total - data.loaded) * 8 / data.bitrate + ) + ' | ' + + this._formatPercentage( + data.loaded / data.total + ) + ' | ' + + this._formatFileSize(data.loaded) + ' / ' + + this._formatFileSize(data.total); + }, + + _hasError: function (file) { + if (file.error) { + return file.error; + } + // The number of added files is subtracted from + // maxNumberOfFiles before validation, so we check if + // maxNumberOfFiles is below 0 (instead of below 1): + if (this.options.maxNumberOfFiles < 0) { + return 'maxNumberOfFiles'; + } + // Files are accepted if either the file type or the file name + // matches against the acceptFileTypes regular expression, as + // only browsers with support for the File API report the type: + if (!(this.options.acceptFileTypes.test(file.type) || + this.options.acceptFileTypes.test(file.name))) { + return 'acceptFileTypes'; + } + if (this.options.maxFileSize && + file.size > this.options.maxFileSize) { + return 'maxFileSize'; + } + if (typeof file.size === 'number' && + file.size < this.options.minFileSize) { + return 'minFileSize'; + } + return null; + }, + + _validate: function (files) { + var that = this, + valid = !!files.length; + $.each(files, function (index, file) { + file.error = that._hasError(file); + if (file.error) { + valid = false; + } + }); + return valid; + }, + + _renderTemplate: function (func, files) { + if (!func) { + return $(); + } + var result = func({ + files: files, + formatFileSize: this._formatFileSize, + options: this.options + }); + if (result instanceof $) { + return result; + } + return $(this.options.templatesContainer).html(result).children(); + }, + + _renderPreview: function (file, node) { + var that = this, + options = this.options, + dfd = $.Deferred(); + return ((loadImage && loadImage( + file, + function (img) { + node.append(img); + that._forceReflow(node); + that._transition(node).done(function () { + dfd.resolveWith(node); + }); + if (!$.contains(document.body, node[0])) { + // If the element is not part of the DOM, + // transition events are not triggered, + // so we have to resolve manually: + dfd.resolveWith(node); + } + }, + { + maxWidth: options.previewMaxWidth, + maxHeight: options.previewMaxHeight, + canvas: options.previewAsCanvas + } + )) || dfd.resolveWith(node)) && dfd; + }, + + _renderPreviews: function (files, nodes) { + var that = this, + options = this.options; + nodes.find('.preview span').each(function (index, element) { + var file = files[index]; + if (options.previewSourceFileTypes.test(file.type) && + ($.type(options.previewSourceMaxFileSize) !== 'number' || + file.size < options.previewSourceMaxFileSize)) { + that._processingQueue = that._processingQueue.pipe(function () { + var dfd = $.Deferred(); + that._renderPreview(file, $(element)).done( + function () { + dfd.resolveWith(that); + } + ); + return dfd.promise(); + }); + } + }); + return this._processingQueue; + }, + + _renderUpload: function (files) { + return this._renderTemplate( + this.options.uploadTemplate, + files + ); + }, + + _renderDownload: function (files) { + return this._renderTemplate( + this.options.downloadTemplate, + files + ).find('a[download]').each(this._enableDragToDesktop).end(); + }, + + _startHandler: function (e) { + e.preventDefault(); + var button = $(this), + template = button.closest('.template-upload'), + data = template.data('data'); + if (data && data.submit && !data.jqXHR && data.submit()) { + button.prop('disabled', true); + } + }, + + _cancelHandler: function (e) { + e.preventDefault(); + var template = $(this).closest('.template-upload'), + data = template.data('data') || {}; + if (!data.jqXHR) { + data.errorThrown = 'abort'; + e.data.fileupload._trigger('fail', e, data); + } else { + data.jqXHR.abort(); + } + }, + + _deleteHandler: function (e) { + e.preventDefault(); + var button = $(this); + e.data.fileupload._trigger('destroy', e, { + context: button.closest('.template-download'), + url: button.attr('data-url'), + type: button.attr('data-type') || 'DELETE', + dataType: e.data.fileupload.options.dataType + }); + }, + + _forceReflow: function (node) { + return $.support.transition && node.length && + node[0].offsetWidth; + }, + + _transition: function (node) { + var dfd = $.Deferred(); + if ($.support.transition && node.hasClass('fade')) { + node.bind( + $.support.transition.end, + function (e) { + // Make sure we don't respond to other transitions events + // in the container element, e.g. from button elements: + if (e.target === node[0]) { + node.unbind($.support.transition.end); + dfd.resolveWith(node); + } + } + ).toggleClass('in'); + } else { + node.toggleClass('in'); + dfd.resolveWith(node); + } + return dfd; + }, + + _initButtonBarEventHandlers: function () { + var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'), + filesList = this.options.filesContainer, + ns = this.options.namespace; + fileUploadButtonBar.find('.start') + .bind('click.' + ns, function (e) { + e.preventDefault(); + filesList.find('.start button').click(); + }); + fileUploadButtonBar.find('.cancel') + .bind('click.' + ns, function (e) { + e.preventDefault(); + filesList.find('.cancel button').click(); + }); + fileUploadButtonBar.find('.delete') + .bind('click.' + ns, function (e) { + e.preventDefault(); + filesList.find('.delete input:checked') + .siblings('button').click(); + fileUploadButtonBar.find('.toggle') + .prop('checked', false); + }); + fileUploadButtonBar.find('.toggle') + .bind('change.' + ns, function (e) { + filesList.find('.delete input').prop( + 'checked', + $(this).is(':checked') + ); + }); + }, + + _destroyButtonBarEventHandlers: function () { + this.element.find('.fileupload-buttonbar button') + .unbind('click.' + this.options.namespace); + this.element.find('.fileupload-buttonbar .toggle') + .unbind('change.' + this.options.namespace); + }, + + _initEventHandlers: function () { + parentWidget.prototype._initEventHandlers.call(this); + var eventData = {fileupload: this}; + this.options.filesContainer + .delegate( + '.start button', + 'click.' + this.options.namespace, + eventData, + this._startHandler + ) + .delegate( + '.cancel button', + 'click.' + this.options.namespace, + eventData, + this._cancelHandler + ) + .delegate( + '.delete button', + 'click.' + this.options.namespace, + eventData, + this._deleteHandler + ); + this._initButtonBarEventHandlers(); + }, + + _destroyEventHandlers: function () { + var options = this.options; + this._destroyButtonBarEventHandlers(); + options.filesContainer + .undelegate('.start button', 'click.' + options.namespace) + .undelegate('.cancel button', 'click.' + options.namespace) + .undelegate('.delete button', 'click.' + options.namespace); + parentWidget.prototype._destroyEventHandlers.call(this); + }, + + _enableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', false) + .parent().removeClass('disabled'); + }, + + _disableFileInputButton: function () { + this.element.find('.fileinput-button input') + .prop('disabled', true) + .parent().addClass('disabled'); + }, + + _initTemplates: function () { + var options = this.options; + options.templatesContainer = document.createElement( + options.filesContainer.prop('nodeName') + ); + if (tmpl) { + if (options.uploadTemplateId) { + options.uploadTemplate = tmpl(options.uploadTemplateId); + } + if (options.downloadTemplateId) { + options.downloadTemplate = tmpl(options.downloadTemplateId); + } + } + }, + + _initFilesContainer: function () { + var options = this.options; + if (options.filesContainer === undefined) { + options.filesContainer = this.element.find('.files'); + } else if (!(options.filesContainer instanceof $)) { + options.filesContainer = $(options.filesContainer); + } + }, + + _initSpecialOptions: function () { + parentWidget.prototype._initSpecialOptions.call(this); + this._initFilesContainer(); + this._initTemplates(); + }, + + _create: function () { + parentWidget.prototype._create.call(this); + this._refreshOptionsList.push( + 'filesContainer', + 'uploadTemplateId', + 'downloadTemplateId' + ); + if (!$.blueimpFP) { + this._processingQueue = $.Deferred().resolveWith(this).promise(); + this.process = function () { + return this._processingQueue; + }; + } + }, + + enable: function () { + parentWidget.prototype.enable.call(this); + this.element.find('input, button').prop('disabled', false); + this._enableFileInputButton(); + }, + + disable: function () { + this.element.find('input, button').prop('disabled', true); + this._disableFileInputButton(); + parentWidget.prototype.disable.call(this); + } + + }); + +})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/jquery.fileupload.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,968 @@ +/* + * jQuery File Upload Plugin 5.12 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2010, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint nomen: true, unparam: true, regexp: true */ +/*global define, window, document, Blob, FormData, location */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define([ + 'jquery', + 'jquery.ui.widget' + ], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // The FileReader API is not actually used, but works as feature detection, + // as e.g. Safari supports XHR file uploads via the FormData API, + // but not non-multipart XHR file uploads: + $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader); + $.support.xhrFormDataFileUpload = !!window.FormData; + + // The fileupload widget listens for change events on file input fields defined + // via fileInput setting and paste or drop events of the given dropZone. + // In addition to the default jQuery Widget methods, the fileupload widget + // exposes the "add" and "send" methods, to add or directly send files using + // the fileupload API. + // By default, files added via file input selection, paste, drag & drop or + // "add" method are uploaded immediately, but it is possible to override + // the "add" callback option to queue file uploads. + $.widget('blueimp.fileupload', { + + options: { + // The namespace used for event handler binding on the dropZone and + // fileInput collections. + // If not set, the name of the widget ("fileupload") is used. + namespace: undefined, + // The drop target collection, by the default the complete document. + // Set to null or an empty collection to disable drag & drop support: + dropZone: $(document), + // The file input field collection, that is listened for change events. + // If undefined, it is set to the file input fields inside + // of the widget element on plugin initialization. + // Set to null or an empty collection to disable the change listener. + fileInput: undefined, + // By default, the file input field is replaced with a clone after + // each input field change event. This is required for iframe transport + // queues and allows change events to be fired for the same file + // selection, but can be disabled by setting the following option to false: + replaceFileInput: true, + // The parameter name for the file form data (the request argument name). + // If undefined or empty, the name property of the file input field is + // used, or "files[]" if the file input name property is also empty, + // can be a string or an array of strings: + paramName: undefined, + // By default, each file of a selection is uploaded using an individual + // request for XHR type uploads. Set to false to upload file + // selections in one request each: + singleFileUploads: true, + // To limit the number of files uploaded with one XHR request, + // set the following option to an integer greater than 0: + limitMultiFileUploads: undefined, + // Set the following option to true to issue all file upload requests + // in a sequential order: + sequentialUploads: false, + // To limit the number of concurrent uploads, + // set the following option to an integer greater than 0: + limitConcurrentUploads: undefined, + // Set the following option to true to force iframe transport uploads: + forceIframeTransport: false, + // Set the following option to the location of a redirect url on the + // origin server, for cross-domain iframe transport uploads: + redirect: undefined, + // The parameter name for the redirect url, sent as part of the form + // data and set to 'redirect' if this option is empty: + redirectParamName: undefined, + // Set the following option to the location of a postMessage window, + // to enable postMessage transport uploads: + postMessage: undefined, + // By default, XHR file uploads are sent as multipart/form-data. + // The iframe transport is always using multipart/form-data. + // Set to false to enable non-multipart XHR uploads: + multipart: true, + // To upload large files in smaller chunks, set the following option + // to a preferred maximum chunk size. If set to 0, null or undefined, + // or the browser does not support the required Blob API, files will + // be uploaded as a whole. + maxChunkSize: undefined, + // When a non-multipart upload or a chunked multipart upload has been + // aborted, this option can be used to resume the upload by setting + // it to the size of the already uploaded bytes. This option is most + // useful when modifying the options object inside of the "add" or + // "send" callbacks, as the options are cloned for each file upload. + uploadedBytes: undefined, + // By default, failed (abort or error) file uploads are removed from the + // global progress calculation. Set the following option to false to + // prevent recalculating the global progress data: + recalculateProgress: true, + // Interval in milliseconds to calculate and trigger progress events: + progressInterval: 100, + // Interval in milliseconds to calculate progress bitrate: + bitrateInterval: 500, + + // Additional form data to be sent along with the file uploads can be set + // using this option, which accepts an array of objects with name and + // value properties, a function returning such an array, a FormData + // object (for XHR file uploads), or a simple object. + // The form of the first fileInput is given as parameter to the function: + formData: function (form) { + return form.serializeArray(); + }, + + // The add callback is invoked as soon as files are added to the fileupload + // widget (via file input selection, drag & drop, paste or add API call). + // If the singleFileUploads option is enabled, this callback will be + // called once for each file in the selection for XHR file uploads, else + // once for each file selection. + // The upload starts when the submit method is invoked on the data parameter. + // The data object contains a files property holding the added files + // and allows one to override plugin options as well as define ajax settings. + // Listeners for this callback can also be bound the following way: + // .bind('fileuploadadd', func); + // data.submit() returns a Promise object and allows one to attach additional + // handlers using jQuery's Deferred callbacks: + // data.submit().done(func).fail(func).always(func); + add: function (e, data) { + data.submit(); + }, + + // Other callbacks: + // Callback for the submit event of each file upload: + // submit: function (e, data) {}, // .bind('fileuploadsubmit', func); + // Callback for the start of each file upload request: + // send: function (e, data) {}, // .bind('fileuploadsend', func); + // Callback for successful uploads: + // done: function (e, data) {}, // .bind('fileuploaddone', func); + // Callback for failed (abort or error) uploads: + // fail: function (e, data) {}, // .bind('fileuploadfail', func); + // Callback for completed (success, abort or error) requests: + // always: function (e, data) {}, // .bind('fileuploadalways', func); + // Callback for upload progress events: + // progress: function (e, data) {}, // .bind('fileuploadprogress', func); + // Callback for global upload progress events: + // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func); + // Callback for uploads start, equivalent to the global ajaxStart event: + // start: function (e) {}, // .bind('fileuploadstart', func); + // Callback for uploads stop, equivalent to the global ajaxStop event: + // stop: function (e) {}, // .bind('fileuploadstop', func); + // Callback for change events of the fileInput collection: + // change: function (e, data) {}, // .bind('fileuploadchange', func); + // Callback for paste events to the dropZone collection: + // paste: function (e, data) {}, // .bind('fileuploadpaste', func); + // Callback for drop events of the dropZone collection: + // drop: function (e, data) {}, // .bind('fileuploaddrop', func); + // Callback for dragover events of the dropZone collection: + // dragover: function (e) {}, // .bind('fileuploaddragover', func); + + // The plugin options are used as settings object for the ajax calls. + // The following are jQuery ajax settings required for the file uploads: + processData: false, + contentType: false, + cache: false + }, + + // A list of options that require a refresh after assigning a new value: + _refreshOptionsList: [ + 'namespace', + 'dropZone', + 'fileInput', + 'multipart', + 'forceIframeTransport' + ], + + _BitrateTimer: function () { + this.timestamp = +(new Date()); + this.loaded = 0; + this.bitrate = 0; + this.getBitrate = function (now, loaded, interval) { + var timeDiff = now - this.timestamp; + if (!this.bitrate || !interval || timeDiff > interval) { + this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8; + this.loaded = loaded; + this.timestamp = now; + } + return this.bitrate; + }; + }, + + _isXHRUpload: function (options) { + return !options.forceIframeTransport && + ((!options.multipart && $.support.xhrFileUpload) || + $.support.xhrFormDataFileUpload); + }, + + _getFormData: function (options) { + var formData; + if (typeof options.formData === 'function') { + return options.formData(options.form); + } + if ($.isArray(options.formData)) { + return options.formData; + } + if (options.formData) { + formData = []; + $.each(options.formData, function (name, value) { + formData.push({name: name, value: value}); + }); + return formData; + } + return []; + }, + + _getTotal: function (files) { + var total = 0; + $.each(files, function (index, file) { + total += file.size || 1; + }); + return total; + }, + + _onProgress: function (e, data) { + if (e.lengthComputable) { + var now = +(new Date()), + total, + loaded; + if (data._time && data.progressInterval && + (now - data._time < data.progressInterval) && + e.loaded !== e.total) { + return; + } + data._time = now; + total = data.total || this._getTotal(data.files); + loaded = parseInt( + e.loaded / e.total * (data.chunkSize || total), + 10 + ) + (data.uploadedBytes || 0); + this._loaded += loaded - (data.loaded || data.uploadedBytes || 0); + data.lengthComputable = true; + data.loaded = loaded; + data.total = total; + data.bitrate = data._bitrateTimer.getBitrate( + now, + loaded, + data.bitrateInterval + ); + // Trigger a custom progress event with a total data property set + // to the file size(s) of the current upload and a loaded data + // property calculated accordingly: + this._trigger('progress', e, data); + // Trigger a global progress event for all current file uploads, + // including ajax calls queued for sequential file uploads: + this._trigger('progressall', e, { + lengthComputable: true, + loaded: this._loaded, + total: this._total, + bitrate: this._bitrateTimer.getBitrate( + now, + this._loaded, + data.bitrateInterval + ) + }); + } + }, + + _initProgressListener: function (options) { + var that = this, + xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr(); + // Accesss to the native XHR object is required to add event listeners + // for the upload progress event: + if (xhr.upload) { + $(xhr.upload).bind('progress', function (e) { + var oe = e.originalEvent; + // Make sure the progress event properties get copied over: + e.lengthComputable = oe.lengthComputable; + e.loaded = oe.loaded; + e.total = oe.total; + that._onProgress(e, options); + }); + options.xhr = function () { + return xhr; + }; + } + }, + + _initXHRData: function (options) { + var formData, + file = options.files[0], + // Ignore non-multipart setting if not supported: + multipart = options.multipart || !$.support.xhrFileUpload, + paramName = options.paramName[0]; + if (!multipart || options.blob) { + // For non-multipart uploads and chunked uploads, + // file meta data is not part of the request body, + // so we transmit this data as part of the HTTP headers. + // For cross domain requests, these headers must be allowed + // via Access-Control-Allow-Headers or removed using + // the beforeSend callback: + options.headers = $.extend(options.headers, { + 'X-File-Name': file.name, + 'X-File-Type': file.type, + 'X-File-Size': file.size + }); + if (!options.blob) { + // Non-chunked non-multipart upload: + options.contentType = file.type; + options.data = file; + } else if (!multipart) { + // Chunked non-multipart upload: + options.contentType = 'application/octet-stream'; + options.data = options.blob; + } + } + if (multipart && $.support.xhrFormDataFileUpload) { + if (options.postMessage) { + // window.postMessage does not allow sending FormData + // objects, so we just add the File/Blob objects to + // the formData array and let the postMessage window + // create the FormData object out of this array: + formData = this._getFormData(options); + if (options.blob) { + formData.push({ + name: paramName, + value: options.blob + }); + } else { + $.each(options.files, function (index, file) { + formData.push({ + name: options.paramName[index] || paramName, + value: file + }); + }); + } + } else { + if (options.formData instanceof FormData) { + formData = options.formData; + } else { + formData = new FormData(); + $.each(this._getFormData(options), function (index, field) { + formData.append(field.name, field.value); + }); + } + if (options.blob) { + formData.append(paramName, options.blob, file.name); + } else { + $.each(options.files, function (index, file) { + // File objects are also Blob instances. + // This check allows the tests to run with + // dummy objects: + if (file instanceof Blob) { + formData.append( + options.paramName[index] || paramName, + file, + file.name + ); + } + }); + } + } + options.data = formData; + } + // Blob reference is not needed anymore, free memory: + options.blob = null; + }, + + _initIframeSettings: function (options) { + // Setting the dataType to iframe enables the iframe transport: + options.dataType = 'iframe ' + (options.dataType || ''); + // The iframe transport accepts a serialized array as form data: + options.formData = this._getFormData(options); + // Add redirect url to form data on cross-domain uploads: + if (options.redirect && $('').prop('href', options.url) + .prop('host') !== location.host) { + options.formData.push({ + name: options.redirectParamName || 'redirect', + value: options.redirect + }); + } + }, + + _initDataSettings: function (options) { + if (this._isXHRUpload(options)) { + if (!this._chunkedUpload(options, true)) { + if (!options.data) { + this._initXHRData(options); + } + this._initProgressListener(options); + } + if (options.postMessage) { + // Setting the dataType to postmessage enables the + // postMessage transport: + options.dataType = 'postmessage ' + (options.dataType || ''); + } + } else { + this._initIframeSettings(options, 'iframe'); + } + }, + + _getParamName: function (options) { + var fileInput = $(options.fileInput), + paramName = options.paramName; + if (!paramName) { + paramName = []; + fileInput.each(function () { + var input = $(this), + name = input.prop('name') || 'files[]', + i = (input.prop('files') || [1]).length; + while (i) { + paramName.push(name); + i -= 1; + } + }); + if (!paramName.length) { + paramName = [fileInput.prop('name') || 'files[]']; + } + } else if (!$.isArray(paramName)) { + paramName = [paramName]; + } + return paramName; + }, + + _initFormSettings: function (options) { + // Retrieve missing options from the input field and the + // associated form, if available: + if (!options.form || !options.form.length) { + options.form = $(options.fileInput.prop('form')); + } + options.paramName = this._getParamName(options); + if (!options.url) { + options.url = options.form.prop('action') || location.href; + } + // The HTTP request method must be "POST" or "PUT": + options.type = (options.type || options.form.prop('method') || '') + .toUpperCase(); + if (options.type !== 'POST' && options.type !== 'PUT') { + options.type = 'POST'; + } + }, + + _getAJAXSettings: function (data) { + var options = $.extend({}, this.options, data); + this._initFormSettings(options); + this._initDataSettings(options); + return options; + }, + + // Maps jqXHR callbacks to the equivalent + // methods of the given Promise object: + _enhancePromise: function (promise) { + promise.success = promise.done; + promise.error = promise.fail; + promise.complete = promise.always; + return promise; + }, + + // Creates and returns a Promise object enhanced with + // the jqXHR methods abort, success, error and complete: + _getXHRPromise: function (resolveOrReject, context, args) { + var dfd = $.Deferred(), + promise = dfd.promise(); + context = context || this.options.context || promise; + if (resolveOrReject === true) { + dfd.resolveWith(context, args); + } else if (resolveOrReject === false) { + dfd.rejectWith(context, args); + } + promise.abort = dfd.promise; + return this._enhancePromise(promise); + }, + + // Uploads a file in multiple, sequential requests + // by splitting the file up in multiple blob chunks. + // If the second parameter is true, only tests if the file + // should be uploaded in chunks, but does not invoke any + // upload requests: + _chunkedUpload: function (options, testOnly) { + var that = this, + file = options.files[0], + fs = file.size, + ub = options.uploadedBytes = options.uploadedBytes || 0, + mcs = options.maxChunkSize || fs, + // Use the Blob methods with the slice implementation + // according to the W3C Blob API specification: + slice = file.webkitSlice || file.mozSlice || file.slice, + upload, + n, + jqXHR, + pipe; + if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) || + options.data) { + return false; + } + if (testOnly) { + return true; + } + if (ub >= fs) { + file.error = 'uploadedBytes'; + return this._getXHRPromise( + false, + options.context, + [null, 'error', file.error] + ); + } + // n is the number of blobs to upload, + // calculated via filesize, uploaded bytes and max chunk size: + n = Math.ceil((fs - ub) / mcs); + // The chunk upload method accepting the chunk number as parameter: + upload = function (i) { + if (!i) { + return that._getXHRPromise(true, options.context); + } + // Upload the blobs in sequential order: + return upload(i -= 1).pipe(function () { + // Clone the options object for each chunk upload: + var o = $.extend({}, options); + o.blob = slice.call( + file, + ub + i * mcs, + ub + (i + 1) * mcs + ); + // Store the current chunk size, as the blob itself + // will be dereferenced after data processing: + o.chunkSize = o.blob.size; + // Process the upload data (the blob and potential form data): + that._initXHRData(o); + // Add progress listeners for this chunk upload: + that._initProgressListener(o); + jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context)) + .done(function () { + // Create a progress event if upload is done and + // no progress event has been invoked for this chunk: + if (!o.loaded) { + that._onProgress($.Event('progress', { + lengthComputable: true, + loaded: o.chunkSize, + total: o.chunkSize + }), o); + } + options.uploadedBytes = o.uploadedBytes += + o.chunkSize; + }); + return jqXHR; + }); + }; + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe = upload(n); + pipe.abort = function () { + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + }, + + _beforeSend: function (e, data) { + if (this._active === 0) { + // the start callback is triggered when an upload starts + // and no other uploads are currently running, + // equivalent to the global ajaxStart event: + this._trigger('start'); + // Set timer for global bitrate progress calculation: + this._bitrateTimer = new this._BitrateTimer(); + } + this._active += 1; + // Initialize the global progress values: + this._loaded += data.uploadedBytes || 0; + this._total += this._getTotal(data.files); + }, + + _onDone: function (result, textStatus, jqXHR, options) { + if (!this._isXHRUpload(options)) { + // Create a progress event for each iframe load: + this._onProgress($.Event('progress', { + lengthComputable: true, + loaded: 1, + total: 1 + }), options); + } + options.result = result; + options.textStatus = textStatus; + options.jqXHR = jqXHR; + this._trigger('done', null, options); + }, + + _onFail: function (jqXHR, textStatus, errorThrown, options) { + options.jqXHR = jqXHR; + options.textStatus = textStatus; + options.errorThrown = errorThrown; + this._trigger('fail', null, options); + if (options.recalculateProgress) { + // Remove the failed (error or abort) file upload from + // the global progress calculation: + this._loaded -= options.loaded || options.uploadedBytes || 0; + this._total -= options.total || this._getTotal(options.files); + } + }, + + _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) { + this._active -= 1; + options.textStatus = textStatus; + if (jqXHRorError && jqXHRorError.always) { + options.jqXHR = jqXHRorError; + options.result = jqXHRorResult; + } else { + options.jqXHR = jqXHRorResult; + options.errorThrown = jqXHRorError; + } + this._trigger('always', null, options); + if (this._active === 0) { + // The stop callback is triggered when all uploads have + // been completed, equivalent to the global ajaxStop event: + this._trigger('stop'); + // Reset the global progress values: + this._loaded = this._total = 0; + this._bitrateTimer = null; + } + }, + + _onSend: function (e, data) { + var that = this, + jqXHR, + slot, + pipe, + options = that._getAJAXSettings(data), + send = function (resolve, args) { + that._sending += 1; + // Set timer for bitrate progress calculation: + options._bitrateTimer = new that._BitrateTimer(); + jqXHR = jqXHR || ( + (resolve !== false && + that._trigger('send', e, options) !== false && + (that._chunkedUpload(options) || $.ajax(options))) || + that._getXHRPromise(false, options.context, args) + ).done(function (result, textStatus, jqXHR) { + that._onDone(result, textStatus, jqXHR, options); + }).fail(function (jqXHR, textStatus, errorThrown) { + that._onFail(jqXHR, textStatus, errorThrown, options); + }).always(function (jqXHRorResult, textStatus, jqXHRorError) { + that._sending -= 1; + that._onAlways( + jqXHRorResult, + textStatus, + jqXHRorError, + options + ); + if (options.limitConcurrentUploads && + options.limitConcurrentUploads > that._sending) { + // Start the next queued upload, + // that has not been aborted: + var nextSlot = that._slots.shift(); + while (nextSlot) { + if (!nextSlot.isRejected()) { + nextSlot.resolve(); + break; + } + nextSlot = that._slots.shift(); + } + } + }); + return jqXHR; + }; + this._beforeSend(e, options); + if (this.options.sequentialUploads || + (this.options.limitConcurrentUploads && + this.options.limitConcurrentUploads <= this._sending)) { + if (this.options.limitConcurrentUploads > 1) { + slot = $.Deferred(); + this._slots.push(slot); + pipe = slot.pipe(send); + } else { + pipe = (this._sequence = this._sequence.pipe(send, send)); + } + // Return the piped Promise object, enhanced with an abort method, + // which is delegated to the jqXHR object of the current upload, + // and jqXHR callbacks mapped to the equivalent Promise methods: + pipe.abort = function () { + var args = [undefined, 'abort', 'abort']; + if (!jqXHR) { + if (slot) { + slot.rejectWith(args); + } + return send(false, args); + } + return jqXHR.abort(); + }; + return this._enhancePromise(pipe); + } + return send(); + }, + + _onAdd: function (e, data) { + var that = this, + result = true, + options = $.extend({}, this.options, data), + limit = options.limitMultiFileUploads, + paramName = this._getParamName(options), + paramNameSet, + paramNameSlice, + fileSet, + i; + if (!(options.singleFileUploads || limit) || + !this._isXHRUpload(options)) { + fileSet = [data.files]; + paramNameSet = [paramName]; + } else if (!options.singleFileUploads && limit) { + fileSet = []; + paramNameSet = []; + for (i = 0; i < data.files.length; i += limit) { + fileSet.push(data.files.slice(i, i + limit)); + paramNameSlice = paramName.slice(i, i + limit); + if (!paramNameSlice.length) { + paramNameSlice = paramName; + } + paramNameSet.push(paramNameSlice); + } + } else { + paramNameSet = paramName; + } + data.originalFiles = data.files; + $.each(fileSet || data.files, function (index, element) { + var newData = $.extend({}, data); + newData.files = fileSet ? element : [element]; + newData.paramName = paramNameSet[index]; + newData.submit = function () { + newData.jqXHR = this.jqXHR = + (that._trigger('submit', e, this) !== false) && + that._onSend(e, this); + return this.jqXHR; + }; + return (result = that._trigger('add', e, newData)); + }); + return result; + }, + + // File Normalization for Gecko 1.9.1 (Firefox 3.5) support: + _normalizeFile: function (index, file) { + if (file.name === undefined && file.size === undefined) { + file.name = file.fileName; + file.size = file.fileSize; + } + }, + + _replaceFileInput: function (input) { + var inputClone = input.clone(true); + $('
      ').append(inputClone)[0].reset(); + // Detaching allows one to insert the fileInput on another form + // without loosing the file input value: + input.after(inputClone).detach(); + // Avoid memory leaks with the detached file input: + $.cleanData(input.unbind('remove')); + // Replace the original file input element in the fileInput + // collection with the clone, which has been copied including + // event handlers: + this.options.fileInput = this.options.fileInput.map(function (i, el) { + if (el === input[0]) { + return inputClone[0]; + } + return el; + }); + // If the widget has been initialized on the file input itself, + // override this.element with the file input clone: + if (input[0] === this.element[0]) { + this.element = inputClone; + } + }, + + _getFileInputFiles: function (fileInput) { + fileInput = $(fileInput); + var files = $.each($.makeArray(fileInput.prop('files')), this._normalizeFile), + value; + if (!files.length) { + value = fileInput.prop('value'); + if (!value) { + return []; + } + // If the files property is not available, the browser does not + // support the File API and we add a pseudo File object with + // the input value as name with path information removed: + files = [{name: value.replace(/^.*\\/, '')}]; + } + return files; + }, + + _onChange: function (e) { + var that = e.data.fileupload, + data = { + fileInput: $(e.target), + form: $(e.target.form) + }; + data.files = that._getFileInputFiles(data.fileInput); + if (that.options.replaceFileInput) { + that._replaceFileInput(data.fileInput); + } + if (that._trigger('change', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } + }, + + _onPaste: function (e) { + var that = e.data.fileupload, + cbd = e.originalEvent.clipboardData, + items = (cbd && cbd.items) || [], + data = {files: []}; + $.each(items, function (index, item) { + var file = item.getAsFile && item.getAsFile(); + if (file) { + data.files.push(file); + } + }); + if (that._trigger('paste', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } + }, + + _onDrop: function (e) { + var that = e.data.fileupload, + dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer, + data = { + files: $.each( + $.makeArray(dataTransfer && dataTransfer.files), + that._normalizeFile + ) + }; + if (that._trigger('drop', e, data) === false || + that._onAdd(e, data) === false) { + return false; + } + e.preventDefault(); + }, + + _onDragOver: function (e) { + var that = e.data.fileupload, + dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer; + if (that._trigger('dragover', e) === false) { + return false; + } + if (dataTransfer) { + dataTransfer.dropEffect = 'copy'; + } + e.preventDefault(); + }, + + _initEventHandlers: function () { + var ns = this.options.namespace; + if (this._isXHRUpload(this.options)) { + this.options.dropZone + .bind('dragover.' + ns, {fileupload: this}, this._onDragOver) + .bind('drop.' + ns, {fileupload: this}, this._onDrop) + .bind('paste.' + ns, {fileupload: this}, this._onPaste); + } + this.options.fileInput + .bind('change.' + ns, {fileupload: this}, this._onChange); + }, + + _destroyEventHandlers: function () { + var ns = this.options.namespace; + this.options.dropZone + .unbind('dragover.' + ns, this._onDragOver) + .unbind('drop.' + ns, this._onDrop) + .unbind('paste.' + ns, this._onPaste); + this.options.fileInput + .unbind('change.' + ns, this._onChange); + }, + + _setOption: function (key, value) { + var refresh = $.inArray(key, this._refreshOptionsList) !== -1; + if (refresh) { + this._destroyEventHandlers(); + } + $.Widget.prototype._setOption.call(this, key, value); + if (refresh) { + this._initSpecialOptions(); + this._initEventHandlers(); + } + }, + + _initSpecialOptions: function () { + var options = this.options; + if (options.fileInput === undefined) { + options.fileInput = this.element.is('input:file') ? + this.element : this.element.find('input:file'); + } else if (!(options.fileInput instanceof $)) { + options.fileInput = $(options.fileInput); + } + if (!(options.dropZone instanceof $)) { + options.dropZone = $(options.dropZone); + } + }, + + _create: function () { + var options = this.options; + // Initialize options set via HTML5 data-attributes: + $.extend(options, $(this.element[0].cloneNode(false)).data()); + options.namespace = options.namespace || this.widgetName; + this._initSpecialOptions(); + this._slots = []; + this._sequence = this._getXHRPromise(true); + this._sending = this._active = this._loaded = this._total = 0; + this._initEventHandlers(); + }, + + destroy: function () { + this._destroyEventHandlers(); + $.Widget.prototype.destroy.call(this); + }, + + enable: function () { + $.Widget.prototype.enable.call(this); + this._initEventHandlers(); + }, + + disable: function () { + this._destroyEventHandlers(); + $.Widget.prototype.disable.call(this); + }, + + // This method is exposed to the widget API and allows adding files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('add', {files: filesList}); + add: function (data) { + if (!data || this.options.disabled) { + return; + } + if (data.fileInput && !data.files) { + data.files = this._getFileInputFiles(data.fileInput); + } else { + data.files = $.each($.makeArray(data.files), this._normalizeFile); + } + this._onAdd(null, data); + }, + + // This method is exposed to the widget API and allows sending files + // using the fileupload API. The data parameter accepts an object which + // must have a files property and can contain additional options: + // .fileupload('send', {files: filesList}); + // The method returns a Promise object for the file upload call. + send: function (data) { + if (data && !this.options.disabled) { + if (data.fileInput && !data.files) { + data.files = this._getFileInputFiles(data.fileInput); + } else { + data.files = $.each($.makeArray(data.files), this._normalizeFile); + } + if (data.files.length) { + return this._onSend(null, data); + } + } + return this._getXHRPromise(false, data && data.context); + } + + }); + +})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/jquery.iframe-transport.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/jquery.iframe-transport.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,171 @@ +/* + * jQuery Iframe Transport Plugin 1.4 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*jslint unparam: true, nomen: true */ +/*global define, window, document */ + +(function (factory) { + 'use strict'; + if (typeof define === 'function' && define.amd) { + // Register as an anonymous AMD module: + define(['jquery'], factory); + } else { + // Browser globals: + factory(window.jQuery); + } +}(function ($) { + 'use strict'; + + // Helper variable to create unique names for the transport iframes: + var counter = 0; + + // The iframe transport accepts three additional options: + // options.fileInput: a jQuery collection of file input fields + // options.paramName: the parameter name for the file form data, + // overrides the name property of the file input field(s), + // can be a string or an array of strings. + // options.formData: an array of objects with name and value properties, + // equivalent to the return data of .serializeArray(), e.g.: + // [{name: 'a', value: 1}, {name: 'b', value: 2}] + $.ajaxTransport('iframe', function (options) { + if (options.async && (options.type === 'POST' || options.type === 'GET')) { + var form, + iframe; + return { + send: function (_, completeCallback) { + form = $('
      '); + // javascript:false as initial iframe src + // prevents warning popups on HTTPS in IE6. + // IE versions below IE8 cannot set the name property of + // elements that have already been added to the DOM, + // so we set the name along with the iframe HTML markup: + iframe = $( + '' + ).bind('load', function () { + var fileInputClones, + paramNames = $.isArray(options.paramName) ? + options.paramName : [options.paramName]; + iframe + .unbind('load') + .bind('load', function () { + var response; + // Wrap in a try/catch block to catch exceptions thrown + // when trying to access cross-domain iframe contents: + try { + response = iframe.contents(); + // Google Chrome and Firefox do not throw an + // exception when calling iframe.contents() on + // cross-domain requests, so we unify the response: + if (!response.length || !response[0].firstChild) { + throw new Error(); + } + } catch (e) { + response = undefined; + } + // The complete callback returns the + // iframe content document as response object: + completeCallback( + 200, + 'success', + {'iframe': response} + ); + // Fix for IE endless progress bar activity bug + // (happens on form submits to iframe targets): + $('') + .appendTo(form); + form.remove(); + }); + form + .prop('target', iframe.prop('name')) + .prop('action', options.url) + .prop('method', options.type); + if (options.formData) { + $.each(options.formData, function (index, field) { + $('') + .prop('name', field.name) + .val(field.value) + .appendTo(form); + }); + } + if (options.fileInput && options.fileInput.length && + options.type === 'POST') { + fileInputClones = options.fileInput.clone(); + // Insert a clone for each file input field: + options.fileInput.after(function (index) { + return fileInputClones[index]; + }); + if (options.paramName) { + options.fileInput.each(function (index) { + $(this).prop( + 'name', + paramNames[index] || options.paramName + ); + }); + } + // Appending the file input fields to the hidden form + // removes them from their original location: + form + .append(options.fileInput) + .prop('enctype', 'multipart/form-data') + // enctype must be set as encoding for IE: + .prop('encoding', 'multipart/form-data'); + } + form.submit(); + // Insert the file input fields at their original location + // by replacing the clones with the originals: + if (fileInputClones && fileInputClones.length) { + options.fileInput.each(function (index, input) { + var clone = $(fileInputClones[index]); + $(input).prop('name', clone.prop('name')); + clone.replaceWith(input); + }); + } + }); + form.append(iframe).appendTo(document.body); + }, + abort: function () { + if (iframe) { + // javascript:false as iframe src aborts the request + // and prevents warning popups on HTTPS in IE6. + // concat is used to avoid the "Script URL" JSLint error: + iframe + .unbind('load') + .prop('src', 'javascript'.concat(':false;')); + } + if (form) { + form.remove(); + } + } + }; + } + }); + + // The iframe transport returns the iframe content document as response. + // The following adds converters from iframe to text, json, html, and script: + $.ajaxSetup({ + converters: { + 'iframe text': function (iframe) { + return $(iframe[0].body).text(); + }, + 'iframe json': function (iframe) { + return $.parseJSON($(iframe[0].body).text()); + }, + 'iframe html': function (iframe) { + return $(iframe[0].body).html(); + }, + 'iframe script': function (iframe) { + return $.globalEval($(iframe[0].body).text()); + } + } + }); + +})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/locale.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/locale.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,29 @@ +/* + * jQuery File Upload Plugin Localization Example 6.5.1 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2012, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +/*global window */ + +window.locale = { + "fileupload": { + "errors": { + "maxFileSize": "File is too big", + "minFileSize": "File is too small", + "acceptFileTypes": "Filetype not allowed", + "maxNumberOfFiles": "Max number of files exceeded", + "uploadedBytes": "Uploaded bytes exceed file size", + "emptyResult": "Empty file upload result" + }, + "error": "Error", + "start": "Start", + "cancel": "Cancel", + "destroy": "Delete" + } +}; diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/vendor/jquery.ui.widget.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery-file-upload/js/vendor/jquery.ui.widget.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,282 @@ +/* + * jQuery UI Widget 1.8.18+amd + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * http://docs.jquery.com/UI/Widget + */ + +(function (factory) { + if (typeof define === "function" && define.amd) { + // Register as an anonymous AMD module: + define(["jquery"], factory); + } else { + // Browser globals: + factory(jQuery); + } +}(function( $, undefined ) { + +// jQuery 1.4+ +if ( $.cleanData ) { + var _cleanData = $.cleanData; + $.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); + }; +} else { + var _remove = $.fn.remove; + $.fn.remove = function( selector, keepData ) { + return this.each(function() { + if ( !keepData ) { + if ( !selector || $.filter( selector, [ this ] ).length ) { + $( "*", this ).add( [ this ] ).each(function() { + try { + $( this ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + }); + } + } + return _remove.call( $(this), selector, keepData ); + }); + }; +} + +$.widget = function( name, base, prototype ) { + var namespace = name.split( "." )[ 0 ], + fullName; + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName ] = function( elem ) { + return !!$.data( elem, name ); + }; + + $[ namespace ] = $[ namespace ] || {}; + $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + + var basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from +// $.each( basePrototype, function( key, val ) { +// if ( $.isPlainObject(val) ) { +// basePrototype[ key ] = $.extend( {}, val ); +// } +// }); + basePrototype.options = $.extend( true, {}, basePrototype.options ); + $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { + namespace: namespace, + widgetName: name, + widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, + widgetBaseClass: fullName + }, prototype ); + + $.widget.bridge( name, $[ namespace ][ name ] ); +}; + +$.widget.bridge = function( name, object ) { + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = Array.prototype.slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.extend.apply( null, [ true, options ].concat(args) ) : + options; + + // prevent calls to internal methods + if ( isMethodCall && options.charAt( 0 ) === "_" ) { + return returnValue; + } + + if ( isMethodCall ) { + this.each(function() { + var instance = $.data( this, name ), + methodValue = instance && $.isFunction( instance[options] ) ? + instance[ options ].apply( instance, args ) : + instance; + // TODO: add this back in 1.9 and use $.error() (see #5972) +// if ( !instance ) { +// throw "cannot call methods on " + name + " prior to initialization; " + +// "attempted to call method '" + options + "'"; +// } +// if ( !$.isFunction( instance[options] ) ) { +// throw "no such method '" + options + "' for " + name + " widget instance"; +// } +// var methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, name ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, name, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( options, element ) { + // allow instantiation without initializing for simple inheritance + if ( arguments.length ) { + this._createWidget( options, element ); + } +}; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + options: { + disabled: false + }, + _createWidget: function( options, element ) { + // $.widget.bridge stores the plugin instance, but we do it anyway + // so that it's stored even before the _create function runs + $.data( element, this.widgetName, this ); + this.element = $( element ); + this.options = $.extend( true, {}, + this.options, + this._getCreateOptions(), + options ); + + var self = this; + this.element.bind( "remove." + this.widgetName, function() { + self.destroy(); + }); + + this._create(); + this._trigger( "create" ); + this._init(); + }, + _getCreateOptions: function() { + return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ]; + }, + _create: function() {}, + _init: function() {}, + + destroy: function() { + this.element + .unbind( "." + this.widgetName ) + .removeData( this.widgetName ); + this.widget() + .unbind( "." + this.widgetName ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetBaseClass + "-disabled " + + "ui-state-disabled" ); + }, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.extend( {}, this.options ); + } + + if (typeof key === "string" ) { + if ( value === undefined ) { + return this.options[ key ]; + } + options = {}; + options[ key ] = value; + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var self = this; + $.each( options, function( key, value ) { + self._setOption( key, value ); + }); + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + [ value ? "addClass" : "removeClass"]( + this.widgetBaseClass + "-disabled" + " " + + "ui-state-disabled" ) + .attr( "aria-disabled", value ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + + return !( $.isFunction(callback) && + callback.call( this.element[0], event, data ) === false || + event.isDefaultPrevented() ); + } +}; + +})); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery.blockui.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery.blockui.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,499 @@ +/*! + * jQuery blockUI plugin + * Version 2.39 (23-MAY-2011) + * @requires jQuery v1.2.3 or later + * + * Examples at: http://malsup.com/jquery/block/ + * Copyright (c) 2007-2010 M. Alsup + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Thanks to Amir-Hossein Sobhi for some excellent contributions! + */ + +;(function($) { + +if (/1\.(0|1|2)\.(0|1|2)/.test($.fn.jquery) || /^1.1/.test($.fn.jquery)) { + alert('blockUI requires jQuery v1.2.3 or later! You are using v' + $.fn.jquery); + return; +} + +$.fn._fadeIn = $.fn.fadeIn; + +var noOp = function() {}; + +// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle +// retarded userAgent strings on Vista) +var mode = document.documentMode || 0; +var setExpr = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8); +var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent) && !mode; + +// global $ methods for blocking/unblocking the entire page +$.blockUI = function(opts) { install(window, opts); }; +$.unblockUI = function(opts) { remove(window, opts); }; + +// convenience method for quick growl-like notifications (http://www.google.com/search?q=growl) +$.growlUI = function(title, message, timeout, onClose) { + var $m = $('
      '); + if (title) $m.append('

      '+title+'

      '); + if (message) $m.append('

      '+message+'

      '); + if (timeout == undefined) timeout = 3000; + $.blockUI({ + message: $m, fadeIn: 700, fadeOut: 1000, centerY: false, + timeout: timeout, showOverlay: false, + onUnblock: onClose, + css: $.blockUI.defaults.growlCSS + }); +}; + +// plugin method for blocking element content +$.fn.block = function(opts) { + return this.unblock({ fadeOut: 0 }).each(function() { + if ($.css(this,'position') == 'static') + this.style.position = 'relative'; + if ($.browser.msie) + this.style.zoom = 1; // force 'hasLayout' + install(this, opts); + }); +}; + +// plugin method for unblocking element content +$.fn.unblock = function(opts) { + return this.each(function() { + remove(this, opts); + }); +}; + +$.blockUI.version = 2.39; // 2nd generation blocking at no extra cost! + +// override these in your code to change the default behavior and style +$.blockUI.defaults = { + // message displayed when blocking (use null for no message) + message: '

      Please wait...

      ', + + title: null, // title string; only used when theme == true + draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded) + + theme: false, // set to true to use with jQuery UI themes + + // styles for the message when blocking; if you wish to disable + // these and use an external stylesheet then do this in your code: + // $.blockUI.defaults.css = {}; + css: { + padding: 0, + margin: 0, + width: '30%', + top: '40%', + left: '35%', + textAlign: 'center', + color: '#000', + border: '3px solid #aaa', + backgroundColor:'#fff', + cursor: 'wait' + }, + + // minimal style set used when themes are used + themedCSS: { + width: '30%', + top: '40%', + left: '35%' + }, + + // styles for the overlay + overlayCSS: { + backgroundColor: '#000', + opacity: 0.6, + cursor: 'wait' + }, + + // styles applied when using $.growlUI + growlCSS: { + width: '350px', + top: '10px', + left: '', + right: '10px', + border: 'none', + padding: '5px', + opacity: 0.6, + cursor: 'default', + color: '#fff', + backgroundColor: '#000', + '-webkit-border-radius': '10px', + '-moz-border-radius': '10px', + 'border-radius': '10px' + }, + + // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w + // (hat tip to Jorge H. N. de Vasconcelos) + iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank', + + // force usage of iframe in non-IE browsers (handy for blocking applets) + forceIframe: false, + + // z-index for the blocking overlay + baseZ: 1000, + + // set these to true to have the message automatically centered + centerX: true, // <-- only effects element blocking (page block controlled via css above) + centerY: true, + + // allow body element to be stetched in ie6; this makes blocking look better + // on "short" pages. disable if you wish to prevent changes to the body height + allowBodyStretch: true, + + // enable if you want key and mouse events to be disabled for content that is blocked + bindEvents: true, + + // be default blockUI will supress tab navigation from leaving blocking content + // (if bindEvents is true) + constrainTabKey: true, + + // fadeIn time in millis; set to 0 to disable fadeIn on block + fadeIn: 200, + + // fadeOut time in millis; set to 0 to disable fadeOut on unblock + fadeOut: 400, + + // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock + timeout: 0, + + // disable if you don't want to show the overlay + showOverlay: true, + + // if true, focus will be placed in the first available input field when + // page blocking + focusInput: true, + + // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity) + applyPlatformOpacityRules: true, + + // callback method invoked when fadeIn has completed and blocking message is visible + onBlock: null, + + // callback method invoked when unblocking has completed; the callback is + // passed the element that has been unblocked (which is the window object for page + // blocks) and the options that were passed to the unblock call: + // onUnblock(element, options) + onUnblock: null, + + // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493 + quirksmodeOffsetHack: 4, + + // class name of the message block + blockMsgClass: 'blockMsg' +}; + +// private data and functions follow... + +var pageBlock = null; +var pageBlockEls = []; + +function install(el, opts) { + var full = (el == window); + var msg = opts && opts.message !== undefined ? opts.message : undefined; + opts = $.extend({}, $.blockUI.defaults, opts || {}); + opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {}); + var css = $.extend({}, $.blockUI.defaults.css, opts.css || {}); + var themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {}); + msg = msg === undefined ? opts.message : msg; + + // remove the current block (if there is one) + if (full && pageBlock) + remove(window, {fadeOut:0}); + + // if an existing element is being used as the blocking content then we capture + // its current place in the DOM (and current display style) so we can restore + // it when we unblock + if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) { + var node = msg.jquery ? msg[0] : msg; + var data = {}; + $(el).data('blockUI.history', data); + data.el = node; + data.parent = node.parentNode; + data.display = node.style.display; + data.position = node.style.position; + if (data.parent) + data.parent.removeChild(node); + } + + $(el).data('blockUI.onUnblock', opts.onUnblock); + var z = opts.baseZ; + + // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform; + // layer1 is the iframe layer which is used to supress bleed through of underlying content + // layer2 is the overlay layer which has opacity and a wait cursor (by default) + // layer3 is the message content that is displayed while blocking + + var lyr1 = ($.browser.msie || opts.forceIframe) + ? $('') + : $(''); + + var lyr2 = opts.theme + ? $('') + : $(''); + + var lyr3, s; + if (opts.theme && full) { + s = ''; + } + else if (opts.theme) { + s = ''; + } + else if (full) { + s = ''; + } + else { + s = ''; + } + lyr3 = $(s); + + // if we have a message, style it + if (msg) { + if (opts.theme) { + lyr3.css(themedCSS); + lyr3.addClass('ui-widget-content'); + } + else + lyr3.css(css); + } + + // style the overlay + if (!opts.theme && (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform)))) + lyr2.css(opts.overlayCSS); + lyr2.css('position', full ? 'fixed' : 'absolute'); + + // make iframe layer transparent in IE + if ($.browser.msie || opts.forceIframe) + lyr1.css('opacity',0.0); + + //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el); + var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el); + $.each(layers, function() { + this.appendTo($par); + }); + + if (opts.theme && opts.draggable && $.fn.draggable) { + lyr3.draggable({ + handle: '.ui-dialog-titlebar', + cancel: 'li' + }); + } + + // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling) + var expr = setExpr && (!$.boxModel || $('object,embed', full ? null : el).length > 0); + if (ie6 || expr) { + // give body 100% height + if (full && opts.allowBodyStretch && $.boxModel) + $('html,body').css('height','100%'); + + // fix ie6 issue when blocked element has a border width + if ((ie6 || !$.boxModel) && !full) { + var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth'); + var fixT = t ? '(0 - '+t+')' : 0; + var fixL = l ? '(0 - '+l+')' : 0; + } + + // simulate fixed position + $.each([lyr1,lyr2,lyr3], function(i,o) { + var s = o[0].style; + s.position = 'absolute'; + if (i < 2) { + full ? s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"') + : s.setExpression('height','this.parentNode.offsetHeight + "px"'); + full ? s.setExpression('width','jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"') + : s.setExpression('width','this.parentNode.offsetWidth + "px"'); + if (fixL) s.setExpression('left', fixL); + if (fixT) s.setExpression('top', fixT); + } + else if (opts.centerY) { + if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"'); + s.marginTop = 0; + } + else if (!opts.centerY && full) { + var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0; + var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"'; + s.setExpression('top',expression); + } + }); + } + + // show the message + if (msg) { + if (opts.theme) + lyr3.find('.ui-widget-content').append(msg); + else + lyr3.append(msg); + if (msg.jquery || msg.nodeType) + $(msg).show(); + } + + if (($.browser.msie || opts.forceIframe) && opts.showOverlay) + lyr1.show(); // opacity is zero + if (opts.fadeIn) { + var cb = opts.onBlock ? opts.onBlock : noOp; + var cb1 = (opts.showOverlay && !msg) ? cb : noOp; + var cb2 = msg ? cb : noOp; + if (opts.showOverlay) + lyr2._fadeIn(opts.fadeIn, cb1); + if (msg) + lyr3._fadeIn(opts.fadeIn, cb2); + } + else { + if (opts.showOverlay) + lyr2.show(); + if (msg) + lyr3.show(); + if (opts.onBlock) + opts.onBlock(); + } + + // bind key and mouse events + bind(1, el, opts); + + if (full) { + pageBlock = lyr3[0]; + pageBlockEls = $(':input:enabled:visible',pageBlock); + if (opts.focusInput) + setTimeout(focus, 20); + } + else + center(lyr3[0], opts.centerX, opts.centerY); + + if (opts.timeout) { + // auto-unblock + var to = setTimeout(function() { + full ? $.unblockUI(opts) : $(el).unblock(opts); + }, opts.timeout); + $(el).data('blockUI.timeout', to); + } +}; + +// remove the block +function remove(el, opts) { + var full = (el == window); + var $el = $(el); + var data = $el.data('blockUI.history'); + var to = $el.data('blockUI.timeout'); + if (to) { + clearTimeout(to); + $el.removeData('blockUI.timeout'); + } + opts = $.extend({}, $.blockUI.defaults, opts || {}); + bind(0, el, opts); // unbind events + + if (opts.onUnblock === null) { + opts.onUnblock = $el.data('blockUI.onUnblock'); + $el.removeData('blockUI.onUnblock'); + } + + var els; + if (full) // crazy selector to handle odd field errors in ie6/7 + els = $('body').children().filter('.blockUI').add('body > .blockUI'); + else + els = $('.blockUI', el); + + if (full) + pageBlock = pageBlockEls = null; + + if (opts.fadeOut) { + els.fadeOut(opts.fadeOut); + setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut); + } + else + reset(els, data, opts, el); +}; + +// move blocking element back into the DOM where it started +function reset(els,data,opts,el) { + els.each(function(i,o) { + // remove via DOM calls so we don't lose event handlers + if (this.parentNode) + this.parentNode.removeChild(this); + }); + + if (data && data.el) { + data.el.style.display = data.display; + data.el.style.position = data.position; + if (data.parent) + data.parent.appendChild(data.el); + $(el).removeData('blockUI.history'); + } + + if (typeof opts.onUnblock == 'function') + opts.onUnblock(el,opts); +}; + +// bind/unbind the handler +function bind(b, el, opts) { + var full = el == window, $el = $(el); + + // don't bother unbinding if there is nothing to unbind + if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked'))) + return; + if (!full) + $el.data('blockUI.isBlocked', b); + + // don't bind events when overlay is not in use or if bindEvents is false + if (!opts.bindEvents || (b && !opts.showOverlay)) + return; + + // bind anchors and inputs for mouse and key events + var events = 'mousedown mouseup keydown keypress'; + b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler); + +// former impl... +// var $e = $('a,:input'); +// b ? $e.bind(events, opts, handler) : $e.unbind(events, handler); +}; + +// event handler to suppress keyboard/mouse events when blocking +function handler(e) { + // allow tab navigation (conditionally) + if (e.keyCode && e.keyCode == 9) { + if (pageBlock && e.data.constrainTabKey) { + var els = pageBlockEls; + var fwd = !e.shiftKey && e.target === els[els.length-1]; + var back = e.shiftKey && e.target === els[0]; + if (fwd || back) { + setTimeout(function(){focus(back)},10); + return false; + } + } + } + var opts = e.data; + // allow events within the message content + if ($(e.target).parents('div.' + opts.blockMsgClass).length > 0) + return true; + + // allow events for content that is not being blocked + return $(e.target).parents().children().filter('div.blockUI').length == 0; +}; + +function focus(back) { + if (!pageBlockEls) + return; + var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0]; + if (e) + e.focus(); +}; + +function center(el, x, y) { + var p = el.parentNode, s = el.style; + var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth'); + var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth'); + if (x) s.left = l > 0 ? (l+'px') : '0'; + if (y) s.top = t > 0 ? (t+'px') : '0'; +}; + +function sz(el, p) { + return parseInt($.css(el,p))||0; +}; + +})(jQuery); diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery.min.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,4 @@ +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
      a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
      "+""+"
      ",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
      t
      ",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
      ",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

      ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
      ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
      ","
      "]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
      ").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery.mobile.min.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery.mobile.min.css Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,2 @@ +/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ +.ui-bar-a{border:1px solid #333;background:#111;color:#fff;font-weight:bold;text-shadow:0 -1px 1px #000;background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#111));background-image:-webkit-linear-gradient(#3c3c3c,#111);background-image:-moz-linear-gradient(#3c3c3c,#111);background-image:-ms-linear-gradient(#3c3c3c,#111);background-image:-o-linear-gradient(#3c3c3c,#111);background-image:linear-gradient(#3c3c3c,#111)}.ui-bar-a,.ui-bar-a input,.ui-bar-a select,.ui-bar-a textarea,.ui-bar-a button{font-family:Helvetica,Arial,sans-serif}.ui-bar-a .ui-link-inherit{color:#fff}.ui-bar-a .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-a .ui-link:hover{color:#2489ce}.ui-bar-a .ui-link:active{color:#2489ce}.ui-bar-a .ui-link:visited{color:#2489ce}.ui-body-a,.ui-overlay-a{border:1px solid #444;background:#222;color:#fff;text-shadow:0 1px 1px #111;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#222));background-image:-webkit-linear-gradient(#444,#222);background-image:-moz-linear-gradient(#444,#222);background-image:-ms-linear-gradient(#444,#222);background-image:-o-linear-gradient(#444,#222);background-image:linear-gradient(#444,#222)}.ui-overlay-a{background-image:none;border-width:0}.ui-body-a,.ui-body-a input,.ui-body-a select,.ui-body-a textarea,.ui-body-a button{font-family:Helvetica,Arial,sans-serif}.ui-body-a .ui-link-inherit{color:#fff}.ui-body-a .ui-link{color:#2489ce;font-weight:bold}.ui-body-a .ui-link:hover{color:#2489ce}.ui-body-a .ui-link:active{color:#2489ce}.ui-body-a .ui-link:visited{color:#2489ce}.ui-btn-up-a{border:1px solid #111;background:#333;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#444),to(#2d2d2d));background-image:-webkit-linear-gradient(#444,#2d2d2d);background-image:-moz-linear-gradient(#444,#2d2d2d);background-image:-ms-linear-gradient(#444,#2d2d2d);background-image:-o-linear-gradient(#444,#2d2d2d);background-image:linear-gradient(#444,#2d2d2d)}.ui-btn-up-a a.ui-link-inherit{color:#fff}.ui-btn-hover-a{border:1px solid #000;background:#444;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#555),to(#383838));background-image:-webkit-linear-gradient(#555,#383838);background-image:-moz-linear-gradient(#555,#383838);background-image:-ms-linear-gradient(#555,#383838);background-image:-o-linear-gradient(#555,#383838);background-image:linear-gradient(#555,#383838)}.ui-btn-hover-a a.ui-link-inherit{color:#fff}.ui-btn-down-a{border:1px solid #000;background:#222;font-weight:bold;color:#fff;text-shadow:0 1px 1px #111;background-image:-webkit-gradient(linear,left top,left bottom,from(#202020),to(#2c2c2c));background-image:-webkit-linear-gradient(#202020,#2c2c2c);background-image:-moz-linear-gradient(#202020,#2c2c2c);background-image:-ms-linear-gradient(#202020,#2c2c2c);background-image:-o-linear-gradient(#202020,#2c2c2c);background-image:linear-gradient(#202020,#2c2c2c)}.ui-btn-down-a a.ui-link-inherit{color:#fff}.ui-btn-up-a,.ui-btn-hover-a,.ui-btn-down-a{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-b{border:1px solid #456f9a;background:#5e87b0;color:#fff;font-weight:bold;text-shadow:0 1px 1px #3e6790;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#497bae));background-image:-webkit-linear-gradient(#6facd5,#497bae);background-image:-moz-linear-gradient(#6facd5,#497bae);background-image:-ms-linear-gradient(#6facd5,#497bae);background-image:-o-linear-gradient(#6facd5,#497bae);background-image:linear-gradient(#6facd5,#497bae)}.ui-bar-b,.ui-bar-b input,.ui-bar-b select,.ui-bar-b textarea,.ui-bar-b button{font-family:Helvetica,Arial,sans-serif}.ui-bar-b .ui-link-inherit{color:#fff}.ui-bar-b .ui-link{color:#ddf0f8;font-weight:bold}.ui-bar-b .ui-link:hover{color:#ddf0f8}.ui-bar-b .ui-link:active{color:#ddf0f8}.ui-bar-b .ui-link:visited{color:#ddf0f8}.ui-body-b,.ui-overlay-b{border:1px solid #999;background:#f3f3f3;color:#222;text-shadow:0 1px 0 #fff;font-weight:normal;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#ccc));background-image:-webkit-linear-gradient(#ddd,#ccc);background-image:-moz-linear-gradient(#ddd,#ccc);background-image:-ms-linear-gradient(#ddd,#ccc);background-image:-o-linear-gradient(#ddd,#ccc);background-image:linear-gradient(#ddd,#ccc)}.ui-overlay-b{background-image:none;border-width:0}.ui-body-b,.ui-body-b input,.ui-body-b select,.ui-body-b textarea,.ui-body-b button{font-family:Helvetica,Arial,sans-serif}.ui-body-b .ui-link-inherit{color:#333}.ui-body-b .ui-link{color:#2489ce;font-weight:bold}.ui-body-b .ui-link:hover{color:#2489ce}.ui-body-b .ui-link:active{color:#2489ce}.ui-body-b .ui-link:visited{color:#2489ce}.ui-btn-up-b{border:1px solid #044062;background:#396b9e;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#5f9cc5),to(#396b9e));background-image:-webkit-linear-gradient(#5f9cc5,#396b9e);background-image:-moz-linear-gradient(#5f9cc5,#396b9e);background-image:-ms-linear-gradient(#5f9cc5,#396b9e);background-image:-o-linear-gradient(#5f9cc5,#396b9e);background-image:linear-gradient(#5f9cc5,#396b9e)}.ui-btn-up-b a.ui-link-inherit{color:#fff}.ui-btn-hover-b{border:1px solid #00415e;background:#4b88b6;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#6facd5),to(#4272a4));background-image:-webkit-linear-gradient(#6facd5,#4272a4);background-image:-moz-linear-gradient(#6facd5,#4272a4);background-image:-ms-linear-gradient(#6facd5,#4272a4);background-image:-o-linear-gradient(#6facd5,#4272a4);background-image:linear-gradient(#6facd5,#4272a4)}.ui-btn-hover-b a.ui-link-inherit{color:#fff}.ui-btn-down-b{border:1px solid #225377;background:#4e89c5;font-weight:bold;color:#fff;text-shadow:0 1px 1px #194b7e;background-image:-webkit-gradient(linear,left top,left bottom,from(#295b8e),to(#3e79b5));background-image:-webkit-linear-gradient(#295b8e,#3e79b5);background-image:-moz-linear-gradient(#295b8e,#3e79b5);background-image:-ms-linear-gradient(#295b8e,#3e79b5);background-image:-o-linear-gradient(#295b8e,#3e79b5);background-image:linear-gradient(#295b8e,#3e79b5)}.ui-btn-down-b a.ui-link-inherit{color:#fff}.ui-btn-up-b,.ui-btn-hover-b,.ui-btn-down-b{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-c{border:1px solid #b3b3b3;background:#eee;color:#3e3e3e;font-weight:bold;text-shadow:0 1px 1px #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#ddd));background-image:-webkit-linear-gradient(#f0f0f0,#ddd);background-image:-moz-linear-gradient(#f0f0f0,#ddd);background-image:-ms-linear-gradient(#f0f0f0,#ddd);background-image:-o-linear-gradient(#f0f0f0,#ddd);background-image:linear-gradient(#f0f0f0,#ddd)}.ui-bar-c .ui-link-inherit{color:#3e3e3e}.ui-bar-c .ui-link{color:#7cc4e7;font-weight:bold}.ui-bar-c .ui-link:hover{color:#2489ce}.ui-bar-c .ui-link:active{color:#2489ce}.ui-bar-c .ui-link:visited{color:#2489ce}.ui-bar-c,.ui-bar-c input,.ui-bar-c select,.ui-bar-c textarea,.ui-bar-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c,.ui-overlay-c{border:1px solid #aaa;color:#333;text-shadow:0 1px 0 #fff;background:#f9f9f9;background-image:-webkit-gradient(linear,left top,left bottom,from(#f9f9f9),to(#eee));background-image:-webkit-linear-gradient(#f9f9f9,#eee);background-image:-moz-linear-gradient(#f9f9f9,#eee);background-image:-ms-linear-gradient(#f9f9f9,#eee);background-image:-o-linear-gradient(#f9f9f9,#eee);background-image:linear-gradient(#f9f9f9,#eee)}.ui-overlay-c{background-image:none;border-width:0}.ui-body-c,.ui-body-c input,.ui-body-c select,.ui-body-c textarea,.ui-body-c button{font-family:Helvetica,Arial,sans-serif}.ui-body-c .ui-link-inherit{color:#333}.ui-body-c .ui-link{color:#2489ce;font-weight:bold}.ui-body-c .ui-link:hover{color:#2489ce}.ui-body-c .ui-link:active{color:#2489ce}.ui-body-c .ui-link:visited{color:#2489ce}.ui-btn-up-c{border:1px solid #ccc;background:#eee;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f1f1f1));background-image:-webkit-linear-gradient(#fff,#f1f1f1);background-image:-moz-linear-gradient(#fff,#f1f1f1);background-image:-ms-linear-gradient(#fff,#f1f1f1);background-image:-o-linear-gradient(#fff,#f1f1f1);background-image:linear-gradient(#fff,#f1f1f1)}.ui-btn-up-c a.ui-link-inherit{color:#2f3e46}.ui-btn-hover-c{border:1px solid #bbb;background:#dfdfdf;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f6f6f6),to(#e0e0e0));background-image:-webkit-linear-gradient(#f9f9f9,#e0e0e0);background-image:-moz-linear-gradient(#f6f6f6,#e0e0e0);background-image:-ms-linear-gradient(#f6f6f6,#e0e0e0);background-image:-o-linear-gradient(#f6f6f6,#e0e0e0);background-image:linear-gradient(#f6f6f6,#e0e0e0)}.ui-btn-hover-c a.ui-link-inherit{color:#2f3e46}.ui-btn-down-c{border:1px solid #bbb;background:#d6d6d6;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#d0d0d0),to(#dfdfdf));background-image:-webkit-linear-gradient(#d0d0d0,#dfdfdf);background-image:-moz-linear-gradient(#d0d0d0,#dfdfdf);background-image:-ms-linear-gradient(#d0d0d0,#dfdfdf);background-image:-o-linear-gradient(#d0d0d0,#dfdfdf);background-image:linear-gradient(#d0d0d0,#dfdfdf)}.ui-btn-down-c a.ui-link-inherit{color:#2f3e46}.ui-btn-up-c,.ui-btn-hover-c,.ui-btn-down-c{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-d{border:1px solid #bbb;background:#bbb;color:#333;text-shadow:0 1px 0 #eee;background-image:-webkit-gradient(linear,left top,left bottom,from(#ddd),to(#bbb));background-image:-webkit-linear-gradient(#ddd,#bbb);background-image:-moz-linear-gradient(#ddd,#bbb);background-image:-ms-linear-gradient(#ddd,#bbb);background-image:-o-linear-gradient(#ddd,#bbb);background-image:linear-gradient(#ddd,#bbb)}.ui-bar-d,.ui-bar-d input,.ui-bar-d select,.ui-bar-d textarea,.ui-bar-d button{font-family:Helvetica,Arial,sans-serif}.ui-bar-d .ui-link-inherit{color:#333}.ui-bar-d .ui-link{color:#2489ce;font-weight:bold}.ui-bar-d .ui-link:hover{color:#2489ce}.ui-bar-d .ui-link:active{color:#2489ce}.ui-bar-d .ui-link:visited{color:#2489ce}.ui-body-d,.ui-overlay-d{border:1px solid #bbb;color:#333;text-shadow:0 1px 0 #fff;background:#fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#fff));background-image:-webkit-linear-gradient(#fff,#fff);background-image:-moz-linear-gradient(#fff,#fff);background-image:-ms-linear-gradient(#fff,#fff);background-image:-o-linear-gradient(#fff,#fff);background-image:linear-gradient(#fff,#fff)}.ui-overlay-d{background-image:none;border-width:0}.ui-body-d,.ui-body-d input,.ui-body-d select,.ui-body-d textarea,.ui-body-d button{font-family:Helvetica,Arial,sans-serif}.ui-body-d .ui-link-inherit{color:#333}.ui-body-d .ui-link{color:#2489ce;font-weight:bold}.ui-body-d .ui-link:hover{color:#2489ce}.ui-body-d .ui-link:active{color:#2489ce}.ui-body-d .ui-link:visited{color:#2489ce}.ui-btn-up-d{border:1px solid #bbb;background:#fff;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#f6f6f6));background-image:-webkit-linear-gradient(#fafafa,#f6f6f6);background-image:-moz-linear-gradient(#fafafa,#f6f6f6);background-image:-ms-linear-gradient(#fafafa,#f6f6f6);background-image:-o-linear-gradient(#fafafa,#f6f6f6);background-image:linear-gradient(#fafafa,#f6f6f6)}.ui-btn-up-d a.ui-link-inherit{color:#333}.ui-btn-hover-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;cursor:pointer;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#eee),to(#fff));background-image:-webkit-linear-gradient(#eee,#fff);background-image:-moz-linear-gradient(#eee,#fff);background-image:-ms-linear-gradient(#eee,#fff);background-image:-o-linear-gradient(#eee,#fff);background-image:linear-gradient(#eee,#fff)}.ui-btn-hover-d a.ui-link-inherit{color:#333}.ui-btn-down-d{border:1px solid #aaa;background:#eee;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#e5e5e5),to(#f2f2f2));background-image:-webkit-linear-gradient(#e5e5e5,#f2f2f2);background-image:-moz-linear-gradient(#e5e5e5,#f2f2f2);background-image:-ms-linear-gradient(#e5e5e5,#f2f2f2);background-image:-o-linear-gradient(#e5e5e5,#f2f2f2);background-image:linear-gradient(#e5e5e5,#f2f2f2)}.ui-btn-down-d a.ui-link-inherit{color:#333}.ui-btn-up-d,.ui-btn-hover-d,.ui-btn-down-d{font-family:Helvetica,Arial,sans-serif;text-decoration:none}.ui-bar-e{border:1px solid #f7c942;background:#fadb4e;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fceda7),to(#fbef7e));background-image:-webkit-linear-gradient(#fceda7,#fbef7e);background-image:-moz-linear-gradient(#fceda7,#fbef7e);background-image:-ms-linear-gradient(#fceda7,#fbef7e);background-image:-o-linear-gradient(#fceda7,#fbef7e);background-image:linear-gradient(#fceda7,#fbef7e)}.ui-bar-e,.ui-bar-e input,.ui-bar-e select,.ui-bar-e textarea,.ui-bar-e button{font-family:Helvetica,Arial,sans-serif}.ui-bar-e .ui-link-inherit{color:#333}.ui-bar-e .ui-link{color:#2489ce;font-weight:bold}.ui-bar-e .ui-link:hover{color:#2489ce}.ui-bar-e .ui-link:active{color:#2489ce}.ui-bar-e .ui-link:visited{color:#2489ce}.ui-body-e,.ui-overlay-e{border:1px solid #f7c942;color:#222;text-shadow:0 1px 0 #fff;background:#fff9df;background-image:-webkit-gradient(linear,left top,left bottom,from(#fffadf),to(#fff3a5));background-image:-webkit-linear-gradient(#fffadf,#fff3a5);background-image:-moz-linear-gradient(#fffadf,#fff3a5);background-image:-ms-linear-gradient(#fffadf,#fff3a5);background-image:-o-linear-gradient(#fffadf,#fff3a5);background-image:linear-gradient(#fffadf,#fff3a5)}.ui-overlay-e{background-image:none;border-width:0}.ui-body-e,.ui-body-e input,.ui-body-e select,.ui-body-e textarea,.ui-body-e button{font-family:Helvetica,Arial,sans-serif}.ui-body-e .ui-link-inherit{color:#333}.ui-body-e .ui-link{color:#2489ce;font-weight:bold}.ui-body-e .ui-link:hover{color:#2489ce}.ui-body-e .ui-link:active{color:#2489ce}.ui-body-e .ui-link:visited{color:#2489ce}.ui-btn-up-e{border:1px solid #f4c63f;background:#fadb4e;font-weight:bold;color:#222;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#ffefaa),to(#ffe155));background-image:-webkit-linear-gradient(#ffefaa,#ffe155);background-image:-moz-linear-gradient(#ffefaa,#ffe155);background-image:-ms-linear-gradient(#ffefaa,#ffe155);background-image:-o-linear-gradient(#ffefaa,#ffe155);background-image:linear-gradient(#ffefaa,#ffe155)}.ui-btn-up-e a.ui-link-inherit{color:#222}.ui-btn-hover-e{border:1px solid #f2c43d;background:#fbe26f;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff5ba),to(#fbdd52));background-image:-webkit-linear-gradient(#fff5ba,#fbdd52);background-image:-moz-linear-gradient(#fff5ba,#fbdd52);background-image:-ms-linear-gradient(#fff5ba,#fbdd52);background-image:-o-linear-gradient(#fff5ba,#fbdd52);background-image:linear-gradient(#fff5ba,#fbdd52)}.ui-btn-hover-e a.ui-link-inherit{color:#333}.ui-btn-down-e{border:1px solid #f2c43d;background:#fceda7;font-weight:bold;color:#111;text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8d94c),to(#fadb4e));background-image:-webkit-linear-gradient(#f8d94c,#fadb4e);background-image:-moz-linear-gradient(#f8d94c,#fadb4e);background-image:-ms-linear-gradient(#f8d94c,#fadb4e);background-image:-o-linear-gradient(#f8d94c,#fadb4e);background-image:linear-gradient(#f8d94c,#fadb4e)}.ui-btn-down-e a.ui-link-inherit{color:#333}.ui-btn-up-e,.ui-btn-hover-e,.ui-btn-down-e{font-family:Helvetica,Arial,sans-serif;text-decoration:none}a.ui-link-inherit{text-decoration:none!important}.ui-btn-active{border:1px solid #2373a5;background:#5393c5;font-weight:bold;color:#fff;cursor:pointer;text-shadow:0 1px 1px #3373a5;text-decoration:none;background-image:-webkit-gradient(linear,left top,left bottom,from(#5393c5),to(#6facd5));background-image:-webkit-linear-gradient(#5393c5,#6facd5);background-image:-moz-linear-gradient(#5393c5,#6facd5);background-image:-ms-linear-gradient(#5393c5,#6facd5);background-image:-o-linear-gradient(#5393c5,#6facd5);background-image:linear-gradient(#5393c5,#6facd5);font-family:Helvetica,Arial,sans-serif}.ui-btn-active a.ui-link-inherit{color:#fff}.ui-btn-inner{border-top:1px solid #fff;border-color:rgba(255,255,255,.3)}.ui-corner-tl{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em}.ui-corner-tr{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bl{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-br{-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-top{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em}.ui-corner-bottom{-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-right{-moz-border-radius-topright:.6em;-webkit-border-top-right-radius:.6em;border-top-right-radius:.6em;-moz-border-radius-bottomright:.6em;-webkit-border-bottom-right-radius:.6em;border-bottom-right-radius:.6em}.ui-corner-left{-moz-border-radius-topleft:.6em;-webkit-border-top-left-radius:.6em;border-top-left-radius:.6em;-moz-border-radius-bottomleft:.6em;-webkit-border-bottom-left-radius:.6em;border-bottom-left-radius:.6em}.ui-corner-all{-moz-border-radius:.6em;-webkit-border-radius:.6em;border-radius:.6em}.ui-corner-none{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.ui-br{border-bottom:#828282;border-bottom:rgba(130,130,130,.3);border-bottom-width:1px;border-bottom-style:solid}.ui-disabled{opacity:.3}.ui-disabled,.ui-disabled a{cursor:default!important;pointer-events:none}.ui-disabled .ui-btn-text{-ms-filter:"alpha(opacity=30)";filter:alpha(opacity=30);zoom:1}.ui-icon,.ui-icon-searchfield:after{background:#666;background:rgba(0,0,0,.4);background-image:url(images/icons-18-white.png);background-repeat:no-repeat;-moz-border-radius:9px;-webkit-border-radius:9px;border-radius:9px}.ui-icon-alt{background:#fff;background:rgba(255,255,255,.3);background-image:url(images/icons-18-black.png);background-repeat:no-repeat}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (min-resolution:240dpi){.ui-icon-plus,.ui-icon-minus,.ui-icon-delete,.ui-icon-arrow-r,.ui-icon-arrow-l,.ui-icon-arrow-u,.ui-icon-arrow-d,.ui-icon-check,.ui-icon-gear,.ui-icon-refresh,.ui-icon-forward,.ui-icon-back,.ui-icon-grid,.ui-icon-star,.ui-icon-alert,.ui-icon-info,.ui-icon-home,.ui-icon-search,.ui-icon-searchfield:after,.ui-icon-checkbox-off,.ui-icon-checkbox-on,.ui-icon-radio-off,.ui-icon-radio-on{background-image:url(images/icons-36-white.png);-moz-background-size:776px 18px;-o-background-size:776px 18px;-webkit-background-size:776px 18px;background-size:776px 18px}.ui-icon-alt{background-image:url(images/icons-36-black.png)}}.ui-icon-plus{background-position:-0 50%}.ui-icon-minus{background-position:-36px 50%}.ui-icon-delete{background-position:-72px 50%}.ui-icon-arrow-r{background-position:-108px 50%}.ui-icon-arrow-l{background-position:-144px 50%}.ui-icon-arrow-u{background-position:-180px 50%}.ui-icon-arrow-d{background-position:-216px 50%}.ui-icon-check{background-position:-252px 50%}.ui-icon-gear{background-position:-288px 50%}.ui-icon-refresh{background-position:-324px 50%}.ui-icon-forward{background-position:-360px 50%}.ui-icon-back{background-position:-396px 50%}.ui-icon-grid{background-position:-432px 50%}.ui-icon-star{background-position:-468px 50%}.ui-icon-alert{background-position:-504px 50%}.ui-icon-info{background-position:-540px 50%}.ui-icon-home{background-position:-576px 50%}.ui-icon-search,.ui-icon-searchfield:after{background-position:-612px 50%}.ui-icon-checkbox-off{background-position:-684px 50%}.ui-icon-checkbox-on{background-position:-648px 50%}.ui-icon-radio-off{background-position:-756px 50%}.ui-icon-radio-on{background-position:-720px 50%}.ui-checkbox .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.ui-icon-checkbox-off,.ui-icon-radio-off{background-color:transparent}.ui-checkbox-on .ui-icon,.ui-radio-on .ui-icon{background-color:#4596ce}.ui-icon-loading{background:url(images/ajax-loader.gif);background-size:46px 46px}.ui-btn-corner-tl{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em}.ui-btn-corner-tr{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bl{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-br{-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-top{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em}.ui-btn-corner-bottom{-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-right{-moz-border-radius-topright:1em;-webkit-border-top-right-radius:1em;border-top-right-radius:1em;-moz-border-radius-bottomright:1em;-webkit-border-bottom-right-radius:1em;border-bottom-right-radius:1em}.ui-btn-corner-left{-moz-border-radius-topleft:1em;-webkit-border-top-left-radius:1em;border-top-left-radius:1em;-moz-border-radius-bottomleft:1em;-webkit-border-bottom-left-radius:1em;border-bottom-left-radius:1em}.ui-btn-corner-all{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em}.ui-corner-tl,.ui-corner-tr,.ui-corner-bl,.ui-corner-br,.ui-corner-top,.ui-corner-bottom,.ui-corner-right,.ui-corner-left,.ui-corner-all,.ui-btn-corner-tl,.ui-btn-corner-tr,.ui-btn-corner-bl,.ui-btn-corner-br,.ui-btn-corner-top,.ui-btn-corner-bottom,.ui-btn-corner-right,.ui-btn-corner-left,.ui-btn-corner-all{-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.ui-overlay{background:#666;opacity:.5;filter:Alpha(Opacity=50);position:absolute;width:100%;height:100%}.ui-overlay-shadow{-moz-box-shadow:0 0 12px rgba(0,0,0,.6);-webkit-box-shadow:0 0 12px rgba(0,0,0,.6);box-shadow:0 0 12px rgba(0,0,0,.6)}.ui-shadow{-moz-box-shadow:0 1px 4px rgba(0,0,0,.3);-webkit-box-shadow:0 1px 4px rgba(0,0,0,.3);box-shadow:0 1px 4px rgba(0,0,0,.3)}.ui-bar-a .ui-shadow,.ui-bar-b .ui-shadow,.ui-bar-c .ui-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.ui-shadow-inset{-moz-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 4px rgba(0,0,0,.2);box-shadow:inset 0 1px 4px rgba(0,0,0,.2)}.ui-icon-shadow{-moz-box-shadow:0 1px 0 rgba(255,255,255,.4);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.4);box-shadow:0 1px 0 rgba(255,255,255,.4)}.ui-btn:focus{outline:0}.ui-focus,.ui-btn:focus{-moz-box-shadow:0 0 12px #387bbe;-webkit-box-shadow:0 0 12px #387bbe;box-shadow:0 0 12px #387bbe}.ui-mobile-nosupport-boxshadow *{-moz-box-shadow:none!important;-webkit-box-shadow:none!important;box-shadow:none!important}.ui-mobile-nosupport-boxshadow .ui-focus,.ui-mobile-nosupport-boxshadow .ui-btn:focus{outline-width:1px;outline-style:dotted}.ui-mobile,.ui-mobile body{height:99.9%}.ui-mobile fieldset,.ui-page{padding:0;margin:0}.ui-mobile a img,.ui-mobile fieldset{border-width:0}.ui-mobile-viewport{margin:0;overflow-x:visible;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}body.ui-mobile-viewport,div.ui-mobile-viewport{overflow-x:hidden}.ui-mobile [data-role=page],.ui-mobile [data-role=dialog],.ui-page{top:0;left:0;width:100%;min-height:100%;position:absolute;display:none;border:0}.ui-mobile .ui-page-active{display:block;overflow:visible}.ui-page{outline:0}@media screen and (orientation:portrait){.ui-mobile,.ui-mobile .ui-page{min-height:420px}}@media screen and (orientation:landscape){.ui-mobile,.ui-mobile .ui-page{min-height:300px}}.ui-loading .ui-loader{display:block}.ui-loader{display:none;z-index:9999999;position:fixed;top:50%;box-shadow:0 1px 1px -1px #fff;left:50%;border:0}.ui-loader-default{background:0;opacity:.18;width:46px;height:46px;margin-left:-23px;margin-top:-23px}.ui-loader-verbose{width:200px;opacity:.88;height:auto;margin-left:-110px;margin-top:-43px;padding:10px}.ui-loader-default h1{font-size:0;width:0;height:0;overflow:hidden}.ui-loader-verbose h1{font-size:16px;margin:0;text-align:center}.ui-loader .ui-icon{background-color:#000;display:block;margin:0;width:44px;height:44px;padding:1px;-webkit-border-radius:36px;-moz-border-radius:36px;border-radius:36px}.ui-loader-verbose .ui-icon{margin:0 auto 10px;opacity:.75}.ui-loader-textonly{padding:15px;margin-left:-115px}.ui-loader-textonly .ui-icon{display:none}.ui-loader-fakefix{position:absolute}.ui-mobile-rendering>*{visibility:hidden}.ui-bar,.ui-body{position:relative;padding:.4em 15px;overflow:hidden;display:block;clear:both}.ui-bar{font-size:16px;margin:0}.ui-bar h1,.ui-bar h2,.ui-bar h3,.ui-bar h4,.ui-bar h5,.ui-bar h6{margin:0;padding:0;font-size:16px;display:inline-block}.ui-header,.ui-footer{position:relative;border-left-width:0;border-right-width:0}.ui-header .ui-btn-left,.ui-header .ui-btn-right,.ui-footer .ui-btn-left,.ui-footer .ui-btn-right{position:absolute;top:3px}.ui-header .ui-btn-left,.ui-footer .ui-btn-left{left:5px}.ui-header .ui-btn-right,.ui-footer .ui-btn-right{right:5px}.ui-footer .ui-btn-icon-notext,.ui-header .ui-btn-icon-notext{top:6px}.ui-header .ui-title,.ui-footer .ui-title{min-height:1.1em;text-align:center;font-size:16px;display:block;margin:.6em 30% .8em;padding:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;outline:0!important}.ui-footer .ui-title{margin:.6em 15px .8em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-icon{width:18px;height:18px}.ui-nojs{position:absolute;left:-9999px}.ui-hide-label label,.ui-hidden-accessible{position:absolute!important;left:-9999px;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ui-mobile-viewport-transitioning,.ui-mobile-viewport-transitioning .ui-page{width:100%;height:100%;overflow:hidden}.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.out{-webkit-animation-timing-function:ease-in;-webkit-animation-duration:225ms;-moz-animation-timing-function:ease-in;-moz-animation-duration:225}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-moz-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadeout{from{opacity:1}to{opacity:0}}@-moz-keyframes fadeout{from{opacity:1}to{opacity:0}}.fade.out{opacity:0;-webkit-animation-duration:125ms;-webkit-animation-name:fadeout;-moz-animation-duration:125ms;-moz-animation-name:fadeout}.fade.in{opacity:1;-webkit-animation-duration:225ms;-webkit-animation-name:fadein;-moz-animation-duration:225ms;-moz-animation-name:fadein}.pop{-webkit-transform-origin:50% 50%;-moz-transform-origin:50% 50%}.pop.in{-webkit-transform:scale(1);-moz-transform:scale(1);opacity:1;-webkit-animation-name:popin;-moz-animation-name:popin;-webkit-animation-duration:350ms;-moz-animation-duration:350ms}.pop.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;opacity:0;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.pop.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein}.pop.out.reverse{-webkit-transform:scale(.8);-moz-transform:scale(.8);-webkit-animation-name:popout;-moz-animation-name:popout}@-webkit-keyframes popin{from{-webkit-transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-moz-keyframes popin{from{-moz-transform:scale(.8);opacity:0}to{-moz-transform:scale(1);opacity:1}}@-webkit-keyframes popout{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(.8);opacity:0}}@-moz-keyframes popout{from{-moz-transform:scale(1);opacity:1}to{-moz-transform:scale(.8);opacity:0}}@-webkit-keyframes slideinfromright{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromright{from{-moz-transform:translateX(100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideinfromleft{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromleft{from{-moz-transform:translateX(-100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideouttoleft{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(-100%)}}@-moz-keyframes slideouttoleft{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(-100%)}}@-webkit-keyframes slideouttoright{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(100%)}}@-moz-keyframes slideouttoright{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(100%)}}.slide.out,.slide.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.slide.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright;-moz-transform:translateX(0);-moz-animation-name:slideinfromright}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft;-moz-transform:translateX(0);-moz-animation-name:slideinfromleft}.slidefade.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft;-webkit-animation-duration:225ms;-moz-animation-duration:225ms}.slidefade.in{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidedown.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;-moz-transform:translateY(0);-moz-animation-name:slideinfromtop;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slidedown.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slidedown.out.reverse{-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-webkit-animation-name:slideouttotop;-moz-animation-name:slideouttotop;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfromtop{from{-webkit-transform:translateY(-100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfromtop{from{-moz-transform:translateY(-100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttotop{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(-100%)}}@-moz-keyframes slideouttotop{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(-100%)}}.slideup.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;-moz-transform:translateY(0);-moz-animation-name:slideinfrombottom;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slideup.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slideup.out.reverse{-webkit-transform:translateY(100%);-moz-transform:translateY(100%);-webkit-animation-name:slideouttobottom;-moz-animation-name:slideouttobottom;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfrombottom{from{-webkit-transform:translateY(100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfrombottom{from{-moz-transform:translateY(100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttobottom{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(100%)}}@-moz-keyframes slideouttobottom{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(100%)}}.viewport-flip{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.flip{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-moz-backface-visibility:hidden;-moz-transform:translateX(0)}.flip.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-webkit-animation-duration:175ms;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-moz-animation-duration:175ms}.flip.in{-webkit-animation-name:flipintoright;-webkit-animation-duration:225ms;-moz-animation-name:flipintoright;-moz-animation-duration:225ms}.flip.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.flip.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.viewport-turn{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.turn{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-webkit-transform-origin:0 0;-moz-backface-visibility:hidden;-moz-transform:translateX(0);-moz-transform-origin:0 0}.turn.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-webkit-animation-duration:125ms;-moz-animation-duration:125ms}.turn.in{-webkit-animation-name:flipintoright;-moz-animation-name:flipintoright;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.turn.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.turn.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.flow{-webkit-transform-origin:50% 30%;-moz-transform-origin:50% 30%;-webkit-box-shadow:0 0 20px rgba(0,0,0,.4);-moz-box-shadow:0 0 20px rgba(0,0,0,.4)}.ui-dialog.flow{-webkit-transform-origin:none;-moz-transform-origin:none;-webkit-box-shadow:none;-moz-box-shadow:none}.flow.out{-webkit-transform:translateX(-100%) scale(.7);-webkit-animation-name:flowouttoleft;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(-100%) scale(.7);-moz-animation-name:flowouttoleft;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.in{-webkit-transform:translateX(0) scale(1);-webkit-animation-name:flowinfromright;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(0) scale(1);-moz-animation-name:flowinfromright;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:flowouttoright;-moz-transform:translateX(100%);-moz-animation-name:flowouttoright}.flow.in.reverse{-webkit-animation-name:flowinfromleft;-moz-animation-name:flowinfromleft}@-webkit-keyframes flowouttoleft{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(-100%) scale(.7)}}@-moz-keyframes flowouttoleft{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(-100%) scale(.7)}}@-webkit-keyframes flowouttoright{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(100%) scale(.7)}}@-moz-keyframes flowouttoright{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(100%) scale(.7)}}@-webkit-keyframes flowinfromleft{0%{-webkit-transform:translateX(-100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromleft{0%{-moz-transform:translateX(-100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}@-webkit-keyframes flowinfromright{0%{-webkit-transform:translateX(100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromright{0%{-moz-transform:translateX(100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d{overflow:hidden}.ui-block-a,.ui-block-b,.ui-block-c,.ui-block-d,.ui-block-e{margin:0;padding:0;border:0;float:left;min-height:1px}.ui-grid-solo .ui-block-a{width:100%;float:none}.ui-grid-a .ui-block-a,.ui-grid-a .ui-block-b{width:50%}.ui-grid-a .ui-block-a{clear:left}.ui-grid-b .ui-block-a,.ui-grid-b .ui-block-b,.ui-grid-b .ui-block-c{width:33.333%}.ui-grid-b .ui-block-a{clear:left}.ui-grid-c .ui-block-a,.ui-grid-c .ui-block-b,.ui-grid-c .ui-block-c,.ui-grid-c .ui-block-d{width:25%}.ui-grid-c .ui-block-a{clear:left}.ui-grid-d .ui-block-a,.ui-grid-d .ui-block-b,.ui-grid-d .ui-block-c,.ui-grid-d .ui-block-d,.ui-grid-d .ui-block-e{width:20%}.ui-grid-d .ui-block-a{clear:left}.ui-header-fixed,.ui-footer-fixed{left:0;right:0;width:100%;position:fixed;z-index:1000}.ui-header-fixed{top:0}.ui-footer-fixed{bottom:0}.ui-header-fullscreen,.ui-footer-fullscreen{opacity:.9}.ui-page-header-fixed{padding-top:2.5em}.ui-page-footer-fixed{padding-bottom:3em}.ui-page-header-fullscreen .ui-content,.ui-page-footer-fullscreen .ui-content{padding:0}.ui-fixed-hidden{position:absolute}.ui-page-header-fullscreen .ui-fixed-hidden,.ui-page-footer-fullscreen .ui-fixed-hidden{left:-99999em}.ui-header-fixed .ui-btn,.ui-footer-fixed .ui-btn{z-index:10}.ui-navbar{overflow:hidden}.ui-navbar ul,.ui-navbar-expanded ul{list-style:none;padding:0;margin:0;position:relative;display:block;border:0}.ui-navbar-collapsed ul{float:left;width:75%;margin-right:-2px}.ui-navbar-collapsed .ui-navbar-toggle{float:left;width:25%}.ui-navbar li.ui-navbar-truncate{position:absolute;left:-9999px;top:-9999px}.ui-navbar li .ui-btn,.ui-navbar .ui-navbar-toggle .ui-btn{display:block;font-size:12px;text-align:center;margin:0;border-right-width:0;max-width:100%}.ui-navbar li .ui-btn{margin-right:-1px}.ui-navbar li .ui-btn:last-child{margin-right:0}.ui-header .ui-navbar li .ui-btn,.ui-header .ui-navbar .ui-navbar-toggle .ui-btn,.ui-footer .ui-navbar li .ui-btn,.ui-footer .ui-navbar .ui-navbar-toggle .ui-btn{border-top-width:0;border-bottom-width:0}.ui-navbar .ui-btn-inner{padding-left:2px;padding-right:2px}.ui-navbar-noicons li .ui-btn .ui-btn-inner,.ui-navbar-noicons .ui-navbar-toggle .ui-btn-inner{padding-top:.8em;padding-bottom:.9em}.ui-navbar-expanded .ui-btn{margin:0;font-size:14px}.ui-navbar-expanded .ui-btn-inner{padding-left:5px;padding-right:5px}.ui-navbar-expanded .ui-btn-icon-top .ui-btn-inner{padding:45px 5px 15px;text-align:center}.ui-navbar-expanded .ui-btn-icon-top .ui-icon{top:15px}.ui-navbar-expanded .ui-btn-icon-bottom .ui-btn-inner{padding:15px 5px 45px;text-align:center}.ui-navbar-expanded .ui-btn-icon-bottom .ui-icon{bottom:15px}.ui-navbar-expanded li .ui-btn .ui-btn-inner{min-height:2.5em}.ui-navbar-expanded .ui-navbar-noicons .ui-btn .ui-btn-inner{padding-top:1.8em;padding-bottom:1.9em}.ui-btn{display:block;text-align:center;cursor:pointer;position:relative;margin:.5em 5px;padding:0}.ui-mini{margin:.25em 5px}.ui-btn-inner{padding:.6em 20px;min-width:.75em;display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;position:relative;zoom:1}.ui-btn input,.ui-btn button{z-index:2}.ui-btn-left,.ui-btn-right,.ui-btn-inline{display:inline-block}.ui-btn-block{display:block}.ui-header .ui-btn,.ui-footer .ui-btn{display:inline-block;margin:0}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-mini .ui-btn-inner{font-size:12.5px;padding:.55em 11px .5em}.ui-header .ui-fullsize .ui-btn-inner,.ui-footer .ui-fullsize .ui-btn-inner{font-size:16px;padding:.6em 25px}.ui-btn-icon-notext{width:24px;height:24px}.ui-btn-icon-notext .ui-btn-inner{padding:0;height:100%}.ui-btn-icon-notext .ui-btn-inner .ui-icon{margin:2px 1px 2px 3px}.ui-btn-text{position:relative;z-index:1;width:100%}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-9999px}.ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-btn-icon-right .ui-btn-inner{padding-right:40px}.ui-btn-icon-top .ui-btn-inner{padding-top:40px}.ui-btn-icon-bottom .ui-btn-inner{padding-bottom:40px}.ui-header .ui-btn-icon-left .ui-btn-inner,.ui-footer .ui-btn-icon-left .ui-btn-inner,.ui-mini .ui-btn-icon-left .ui-btn-inner{padding-left:30px}.ui-header .ui-btn-icon-right .ui-btn-inner,.ui-footer .ui-btn-icon-right .ui-btn-inner,.ui-mini .ui-btn-icon-right .ui-btn-inner{padding-right:30px}.ui-header .ui-btn-icon-top .ui-btn-inner,.ui-footer .ui-btn-icon-top .ui-btn-inner,.ui-mini .ui-btn-icon-top .ui-btn-inner{padding:30px 3px .5em 3px}.ui-header .ui-btn-icon-bottom .ui-btn-inner,.ui-footer .ui-btn-icon-bottom .ui-btn-inner,.ui-mini .ui-btn-icon-bottom .ui-btn-inner{padding:.55em 3px 30px 3px}.ui-btn-icon-notext .ui-icon{display:block;z-index:0}.ui-btn-icon-left .ui-btn-inner .ui-icon,.ui-btn-icon-right .ui-btn-inner .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-btn-icon-top .ui-btn-inner .ui-icon,.ui-btn-icon-bottom .ui-btn-inner .ui-icon{position:absolute;left:50%;margin-left:-9px}.ui-btn-icon-left .ui-icon{left:10px}.ui-btn-icon-right .ui-icon{right:10px}.ui-btn-icon-top .ui-icon{top:10px}.ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-header .ui-btn-icon-left .ui-icon,.ui-footer .ui-btn-icon-left .ui-icon,.ui-mini.ui-btn-icon-left .ui-icon,.ui-mini .ui-btn-icon-left .ui-icon{left:5px}.ui-header .ui-btn-icon-right .ui-icon,.ui-footer .ui-btn-icon-right .ui-icon,.ui-mini.ui-btn-icon-right .ui-icon,.ui-mini .ui-btn-icon-right .ui-icon{right:5px}.ui-header .ui-btn-icon-top .ui-icon,.ui-footer .ui-btn-icon-top .ui-icon,.ui-mini.ui-btn-icon-top .ui-icon,.ui-mini .ui-btn-icon-top .ui-icon{top:5px}.ui-header .ui-btn-icon-bottom .ui-icon,.ui-footer .ui-btn-icon-bottom .ui-icon,.ui-mini.ui-btn-icon-bottom .ui-icon,.ui-mini .ui-btn-icon-bottom .ui-icon{bottom:5px}.ui-btn-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:button;opacity:.1;cursor:pointer;background:#fff;background:rgba(255,255,255,0);filter:Alpha(Opacity=.0001);font-size:1px;border:0;text-indent:-9999px}.ui-collapsible{margin:.5em 0}.ui-collapsible-heading{font-size:16px;display:block;margin:0 -8px;padding:0;border-width:0 0 1px 0;position:relative}.ui-collapsible-heading a{text-align:left;margin:0}.ui-collapsible-heading .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-collapsible-heading .ui-btn-icon-right .ui-btn-inner{padding-left:12px;padding-right:40px}.ui-collapsible-heading .ui-btn-icon-top .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-bottom .ui-btn-inner{padding-right:40px;text-align:center}.ui-collapsible-heading a span.ui-btn{position:absolute;left:6px;top:50%;margin:-12px 0 0 0;width:20px;height:20px;padding:1px 0 1px 2px;text-indent:-9999px}.ui-collapsible-heading a span.ui-btn .ui-btn-inner{padding:10px 0}.ui-collapsible-heading a span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:absolute;top:-9999px;left:0}.ui-collapsible-content{display:block;margin:0 -8px;padding:10px 16px;border-top:0;background-image:none;font-weight:normal}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible{margin:-1px 0 0}.ui-controlgroup,fieldset.ui-controlgroup{padding:0;margin:0 0 .5em;zoom:1}.ui-bar .ui-controlgroup{margin:0 .3em}.ui-controlgroup-label{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .4em}.ui-controlgroup-controls{display:block;width:100%}.ui-controlgroup li{list-style:none}.ui-controlgroup-vertical .ui-btn,.ui-controlgroup-vertical .ui-checkbox,.ui-controlgroup-vertical .ui-radio{margin:0;border-bottom-width:0}.ui-controlgroup-controls label.ui-select{position:absolute;left:-9999px}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-horizontal{padding:0}.ui-controlgroup-horizontal .ui-btn-inner{text-align:center}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-select{display:inline-block;margin:0 -6px 0 0}.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{float:left;clear:none;margin:0 -1px 0 0}.ui-controlgroup-horizontal .ui-checkbox .ui-btn,.ui-controlgroup-horizontal .ui-radio .ui-btn,.ui-controlgroup-horizontal .ui-checkbox:last-child,.ui-controlgroup-horizontal .ui-radio:last-child{margin-right:0}.ui-controlgroup-horizontal .ui-controlgroup-last{margin-right:0}.ui-controlgroup .ui-checkbox label,.ui-controlgroup .ui-radio label{font-size:16px}@media all and (min-width:450px){.ui-field-contain .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-controlgroup-controls{width:60%;display:inline-block}.ui-field-contain .ui-controlgroup .ui-select{width:100%}.ui-field-contain .ui-controlgroup-horizontal .ui-select{width:auto}}.ui-dialog{background:none!important}.ui-dialog-contain{width:92.5%;max-width:500px;margin:10% auto 15px auto;padding:0}.ui-dialog .ui-header{margin-top:15%;border:0;overflow:hidden}.ui-dialog .ui-header,.ui-dialog .ui-content,.ui-dialog .ui-footer{display:block;position:relative;width:auto}.ui-dialog .ui-header,.ui-dialog .ui-footer{z-index:10;padding:0}.ui-dialog .ui-footer{padding:0 15px}.ui-dialog .ui-content{padding:15px}.ui-dialog{margin-top:-15px}.ui-checkbox,.ui-radio{position:relative;clear:both;margin:.2em 0 .5em;z-index:1}.ui-checkbox .ui-btn,.ui-radio .ui-btn{margin:0;text-align:left;z-index:2}.ui-checkbox .ui-btn-inner,.ui-radio .ui-btn-inner{white-space:normal}.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner{padding-left:45px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-left .ui-btn-inner{padding-left:36px}.ui-checkbox .ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-right .ui-btn-inner{padding-right:36px}.ui-checkbox .ui-btn-icon-top .ui-btn-inner,.ui-radio .ui-btn-icon-top .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-btn-icon-bottom .ui-btn-inner,.ui-radio .ui-btn-icon-bottom .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-icon,.ui-radio .ui-icon{top:1.1em}.ui-checkbox .ui-btn-icon-left .ui-icon,.ui-radio .ui-btn-icon-left .ui-icon{left:15px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-icon,.ui-radio .ui-mini.ui-btn-icon-left .ui-icon{left:9px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox .ui-btn-icon-top .ui-icon,.ui-radio .ui-btn-icon-top .ui-icon{top:10px}.ui-checkbox .ui-btn-icon-bottom .ui-icon,.ui-radio .ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox input,.ui-radio input{position:absolute;left:20px;top:50%;width:10px;height:10px;margin:-5px 0 0 0;outline:0!important;z-index:1}.ui-field-contain,fieldset.ui-field-contain{padding:.8em 0;margin:0;border-width:0 0 1px 0;overflow:visible}.ui-field-contain:first-child{border-top-width:0}.ui-header .ui-field-contain-left,.ui-header .ui-field-contain-right{position:absolute;top:0;width:25%}.ui-header .ui-field-contain-left{left:1em}.ui-header .ui-field-contain-right{right:1em}@media all and (min-width:450px){.ui-field-contain,.ui-mobile fieldset.ui-field-contain{border-width:0;padding:0;margin:1em 0}}.ui-select{display:block;position:relative}.ui-select select{position:absolute;left:-9999px;top:-9999px}.ui-select .ui-btn{overflow:hidden;opacity:1;margin:0}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:button;left:0;top:0;width:100%;min-height:1.5em;min-height:100%;height:3em;max-height:100%;opacity:0;-ms-filter:"alpha(opacity=0)";filter:alpha(opacity=0);z-index:2}.ui-select .ui-disabled{opacity:.3}@-moz-document url-prefix(){.ui-select .ui-btn select{opacity:.0001}}.ui-select .ui-btn select.ui-select-nativeonly{opacity:1;text-indent:0}.ui-select .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-select .ui-btn-icon-right .ui-icon{right:15px}.ui-select .ui-mini.ui-btn-icon-right .ui-icon{right:7px}label.ui-select{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}.ui-select .ui-btn-text,.ui-selectmenu .ui-btn-text{display:block;min-height:1em;overflow:hidden!important}.ui-select .ui-btn-text{text-overflow:ellipsis}.ui-selectmenu{position:absolute;padding:0;z-index:1100!important;width:80%;max-width:350px;padding:6px}.ui-selectmenu .ui-listview{margin:0}.ui-selectmenu .ui-btn.ui-li-divider{cursor:default}.ui-selectmenu-hidden{top:-9999px;left:-9999px}.ui-selectmenu-screen{position:absolute;top:0;left:0;width:100%;height:100%;z-index:99}.ui-screen-hidden,.ui-selectmenu-list .ui-li .ui-icon{display:none}.ui-selectmenu-list .ui-li .ui-icon{display:block}.ui-li.ui-selectmenu-placeholder{display:none}.ui-selectmenu .ui-header .ui-title{margin:.6em 46px .8em}@media all and (min-width:450px){.ui-field-contain label.ui-select{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-select{width:60%;display:inline-block}}.ui-selectmenu .ui-header h1:after{content:'.';visibility:hidden}label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}input.ui-input-text,textarea.ui-input-text{background-image:none;padding:.4em;line-height:1.4;font-size:16px;display:block;width:97%;outline:0}.ui-header input.ui-input-text,.ui-footer input.ui-input-text{margin-left:1.25%;padding:.4em 1%;width:95.5%}input.ui-input-text{-webkit-appearance:none}textarea.ui-input-text{height:50px;-webkit-transition:height 200ms linear;-moz-transition:height 200ms linear;-o-transition:height 200ms linear;transition:height 200ms linear}.ui-input-search{padding:0 30px;background-image:none;position:relative}.ui-icon-searchfield:after{position:absolute;left:7px;top:50%;margin-top:-9px;content:"";width:18px;height:18px;opacity:.5}.ui-input-search input.ui-input-text{border:0;width:98%;padding:.4em 0;margin:0;display:block;background:transparent none;outline:0!important}.ui-input-search .ui-input-clear{position:absolute;right:0;top:50%;margin-top:-13px}.ui-mini .ui-input-clear{right:-3px}.ui-input-search .ui-input-clear-hidden{display:none}input.ui-mini,.ui-mini input,textarea.ui-mini{font-size:14px}textarea.ui-mini{height:45px}@media all and (min-width:450px){.ui-field-contain label.ui-input-text{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text,.ui-field-contain .ui-input-search{width:60%;display:inline-block}.ui-field-contain .ui-input-search{width:50%}.ui-hide-label input.ui-input-text,.ui-hide-label textarea.ui-input-text,.ui-hide-label .ui-input-search{padding:.4em;width:97%}.ui-input-search input.ui-input-text{width:98%}}.ui-listview{margin:0;counter-reset:listnumbering}.ui-content .ui-listview{margin:-15px}.ui-content .ui-listview-inset{margin:1em 0}.ui-listview,.ui-li{list-style:none;padding:0}.ui-li,.ui-li.ui-field-contain{display:block;margin:0;position:relative;overflow:visible;text-align:left;border-width:0;border-top-width:1px}.ui-li .ui-btn-text a.ui-link-inherit{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-divider,.ui-li-static{padding:.5em 15px;font-size:14px;font-weight:bold}.ui-li-divider{counter-reset:listnumbering}ol.ui-listview .ui-link-inherit:before,ol.ui-listview .ui-li-static:before,.ui-li-dec{font-size:.8em;display:inline-block;padding-right:.3em;font-weight:normal;counter-increment:listnumbering;content:counter(listnumbering) ". "}ol.ui-listview .ui-li-jsnumbering:before{content:""!important}.ui-listview-inset .ui-li{border-right-width:1px;border-left-width:1px}.ui-li:last-child,.ui-li.ui-field-contain:last-child{border-bottom-width:1px}.ui-li>.ui-btn-inner{display:block;position:relative;padding:0}.ui-li .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li{padding:.7em 15px .7em 15px;display:block}.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-thumb{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-icon{min-height:20px;padding-left:40px}.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-count{padding-right:45px}.ui-li-has-arrow .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow{padding-right:30px}.ui-li-has-arrow.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow.ui-li-has-count{padding-right:75px}.ui-li-has-count .ui-btn-text{padding-right:15px}.ui-li-heading{font-size:16px;font-weight:bold;display:block;margin:.6em 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-desc{font-size:12px;font-weight:normal;display:block;margin:-.5em 0 .6em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-thumb,.ui-listview .ui-li-icon{position:absolute;left:1px;top:0;max-height:80px;max-width:80px}.ui-listview .ui-li-icon{max-height:40px;max-width:40px;left:10px;top:.9em}.ui-li-thumb,.ui-listview .ui-li-icon,.ui-li-content{float:left;margin-right:10px}.ui-li-aside{float:right;width:50%;text-align:right;margin:.3em 0}@media all and (min-width:480px){.ui-li-aside{width:45%}}.ui-li-divider{cursor:default}.ui-li-has-alt .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt{padding-right:95px}.ui-li-has-count .ui-li-count{position:absolute;font-size:11px;font-weight:bold;padding:.2em .5em;top:50%;margin-top:-.9em;right:48px}.ui-li-divider .ui-li-count,.ui-li-static .ui-li-count{right:10px}.ui-li-has-alt .ui-li-count{right:55px}.ui-li-link-alt{position:absolute;width:40px;height:100%;border-width:0;border-left-width:1px;top:0;right:0;margin:0;padding:0;z-index:2}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-11px 0 0 0;border-bottom-width:1px;z-index:-1}.ui-li-link-alt .ui-btn-inner{padding:0;height:100%;position:absolute;width:100%;top:0;left:0}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-listview * .ui-btn-inner>.ui-btn>.ui-btn-inner{border-top:0}.ui-listview-filter{border-width:0;overflow:hidden;margin:-15px -15px 15px -15px}.ui-listview-filter .ui-input-search{margin:5px;width:auto;display:block}.ui-listview-filter-inset{margin:-15px -5px -15px -5px;background:transparent}.ui-li.ui-screen-hidden{display:none}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}input.ui-slider-input,.ui-field-contain input.ui-slider-input{display:inline-block;width:50px}select.ui-slider-switch{display:none}div.ui-slider{position:relative;display:inline-block;overflow:visible;height:15px;padding:0;margin:0 2% 0 20px;top:4px;width:65%}div.ui-slider-mini{height:12px;margin-left:10px}div.ui-slider-bg{border:0;height:100%;padding-right:8px}.ui-controlgroup a.ui-slider-handle,a.ui-slider-handle{position:absolute;z-index:1;top:50%;width:28px;height:28px;margin-top:-15px;margin-left:-15px;outline:0}a.ui-slider-handle .ui-btn-inner{padding:0;height:100%}div.ui-slider-mini a.ui-slider-handle{height:14px;width:14px;margin:-8px 0 0 -7px}div.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:-9px 0 0 -9px}@media all and (min-width:450px){.ui-field-contain label.ui-slider{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain div.ui-slider{width:43%}.ui-field-contain div.ui-slider-switch{width:5.5em}}div.ui-slider-switch{height:32px;margin-left:0;width:5.8em}a.ui-slider-handle-snapping{-webkit-transition:left 70ms linear;-moz-transition:left 70ms linear}div.ui-slider-switch .ui-slider-handle{margin-top:1px}.ui-slider-inneroffset{margin:0 16px;position:relative;z-index:1}div.ui-slider-switch.ui-slider-mini{width:5em;height:29px}div.ui-slider-switch.ui-slider-mini .ui-slider-inneroffset{margin:0 15px 0 14px}div.ui-slider-switch.ui-slider-mini .ui-slider-handle{width:25px;height:25px;margin:1px 0 0 -13px}div.ui-slider-switch.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:0}span.ui-slider-label{position:absolute;text-align:center;width:100%;overflow:hidden;font-size:16px;top:0;line-height:2;min-height:100%;border-width:0;white-space:nowrap}.ui-slider-mini span.ui-slider-label{font-size:14px}span.ui-slider-label-a{z-index:1;left:0;text-indent:-1.5em}span.ui-slider-label-b{z-index:0;right:0;text-indent:1.5em}.ui-slider-inline{width:120px;display:inline-block} \ No newline at end of file diff -r 6c6239aec462 -r d25f4c0fa160 OrthancServer/OrthancExplorer/libs/jquery.mobile.min.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancExplorer/libs/jquery.mobile.min.js Wed Jun 10 20:30:34 2020 +0200 @@ -0,0 +1,177 @@ +/*! jQuery Mobile v1.1.0 db342b1f315c282692791aa870455901fdb46a55 jquerymobile.com | jquery.org/license */ +(function(D,s,k){typeof define==="function"&&define.amd?define(["jquery"],function(a){k(a,D,s);return a.mobile}):k(D.jQuery,D,s)})(this,document,function(D,s,k){(function(a,c,b,e){function f(a){for(;a&&typeof a.originalEvent!=="undefined";)a=a.originalEvent;return a}function d(b){for(var d={},f,g;b;){f=a.data(b,t);for(g in f)if(f[g])d[g]=d.hasVirtualBinding=true;b=b.parentNode}return d}function g(){y&&(clearTimeout(y),y=0);y=setTimeout(function(){F=y=0;C.length=0;z=false;G=true},a.vmouse.resetTimerDuration)} +function h(b,d,g){var c,h;if(!(h=g&&g[b])){if(g=!g)a:{for(g=d.target;g;){if((h=a.data(g,t))&&(!b||h[b]))break a;g=g.parentNode}g=null}h=g}if(h){c=d;var g=c.type,z,j;c=a.Event(c);c.type=b;h=c.originalEvent;z=a.event.props;g.search(/^(mouse|click)/)>-1&&(z=w);if(h)for(j=z.length;j;)b=z[--j],c[b]=h[b];if(g.search(/mouse(down|up)|click/)>-1&&!c.which)c.which=1;if(g.search(/^touch/)!==-1&&(b=f(h),g=b.touches,b=b.changedTouches,g=g&&g.length?g[0]:b&&b.length?b[0]:e))for(h=0,len=u.length;hz||Math.abs(c.pageY-E)>z;flags=d(b.target);A&&!e&&h("vmousecancel",b,flags);h("vmousemove",b,flags);g()}}function l(a){if(!G){G=true;var b=d(a.target),c;h("vmouseup",a,b);if(!A&&(c=h("vclick",a,b))&&c.isDefaultPrevented())c=f(a).changedTouches[0],C.push({touchID:F,x:c.clientX,y:c.clientY}),z=true;h("vmouseout",a,b);A=false;g()}}function r(b){var b= +a.data(b,t),d;if(b)for(d in b)if(b[d])return true;return false}function n(){}function k(b){var d=b.substr(1);return{setup:function(){r(this)||a.data(this,t,{});a.data(this,t)[b]=true;v[b]=(v[b]||0)+1;v[b]===1&&H.bind(d,j);a(this).bind(d,n);if(K)v.touchstart=(v.touchstart||0)+1,v.touchstart===1&&H.bind("touchstart",o).bind("touchend",l).bind("touchmove",p).bind("scroll",m)},teardown:function(){--v[b];v[b]||H.unbind(d,j);K&&(--v.touchstart,v.touchstart||H.unbind("touchstart",o).unbind("touchmove",p).unbind("touchend", +l).unbind("scroll",m));var f=a(this),g=a.data(this,t);g&&(g[b]=false);f.unbind(d,n);r(this)||f.removeData(t)}}}var t="virtualMouseBindings",x="virtualTouchID",c="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),u="clientX clientY pageX pageY screenX screenY".split(" "),w=a.event.props.concat(a.event.mouseHooks?a.event.mouseHooks.props:[]),v={},y=0,s=0,E=0,A=false,C=[],z=false,G=false,K="addEventListener"in b,H=a(b),L=1,F=0;a.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10, +resetTimerDuration:1500};for(var I=0;I7);a.fn[f]=function(a){return a?this.bind(f,a):this.trigger(f)};a.fn[f].delay=50;h[f]=a.extend(h[f],{setup:function(){if(o)return false;a(g.start)},teardown:function(){if(o)return false;a(g.stop)}});g=function(){function g(){var b=e(),d=t(r);if(b!==r)k(r=b,d),a(c).trigger(f);else if(d!==r)location.href=location.href.replace(/#.*/,"")+d;j=setTimeout(g,a.fn[f].delay)}var h={},j,r=e(),n=function(a){return a},k= +n,t=n;h.start=function(){j||g()};h.stop=function(){j&&clearTimeout(j);j=b};a.browser.msie&&!o&&function(){var b,c;h.start=function(){if(!b)c=(c=a.fn[f].src)&&c+e(),b=a('