pax_global_header00006660000000000000000000000064147477772000014531gustar00rootroot0000000000000052 comment=62051395787af9fb6cda2133c24baf7b02aee6fa megatools-1.11.3.20250203/000077500000000000000000000000001474777720000145025ustar00rootroot00000000000000megatools-1.11.3.20250203/.editorconfig000066400000000000000000000001631474777720000171570ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 indent_style = space indent_size = 2 megatools-1.11.3.20250203/.gitignore000066400000000000000000000004431474777720000164730ustar00rootroot00000000000000/Makefile /Makefile.in .deps .libs .dirstamp *.o *.lo *.la *~ *.a /aclocal.m4 /autom4te.cache /config.* /configure /depcomp /install-sh /missing /stamp-h1 /compile /m4 /megatools /tests/tools/*.dat /tests/tools/Mount /tests/tools/TestDir /.vimrc /*.tar.xz /*.tar.gz /docs/*.1 /docs/*.5 megatools-1.11.3.20250203/LICENSE000066400000000000000000000446221474777720000155170ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE 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. 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 convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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. OpenSSL Exemption ----------------- In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library under certain conditions as described in each individual source file, and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify file(s) with this exception, you may extend this exception to your version of the file(s), but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. megatools-1.11.3.20250203/NEWS000066400000000000000000000456051474777720000152130ustar00rootroot00000000000000megatools 1.11.3 - 2025-02-03 ============================= This release changes the location of the project website, and email address of the maintainer across documentation. megatools 1.11.1 - 2023-02-12 ============================= This is just a bugfix release. Fixes: - fix folder downloads - fix handling of = in mega URLs megatools 1.11.0 - 2022-05-19 ============================= This release introduces new features. Improvements: - Megatools now install a single `megatools` executable, where former `megaXYZ ...` commands now have to be executed as `megatools XYZ ...`. - Implemented PKP (public key pinning for mega.nz's API server). Megatools no longer use public certificate authorities to verify API server's authenticity. If you use software that MITM's your web traffic, megatools will not allow this anymore. - Windows builds no longer need any special console setup to support UTF-8. It just works out of the box. - Support new public folder URLs in `dl` - Build system moved to meson - New `test` command for checking if file exists - New `export` command for creating public links for private files - Login v2 support - Upload reliability improvements Commit list: 9294db2 Improve support for UTF-8 on windows 82833a2 Use g_memdup2 4adc57f Try disabling TLS session ID cache (may fix #462) 3f59e27 add support for newstyle folders 8b759a3 Cleanup the dl range code e299c43 Add range selection to the downloader 539ff6f Return status = 2 if uploaded file already exists c0bd7ef Add file exists error type 8d91a61 Fix NULL pointer dereference when invoking megatools in some situations 57cddce Bump cache format version 1737c55 Cleanup login v2 support and fix session caching bf886a4 Add contributed support for v2 login flow e4e8495 dl: Support new link format for individual files inside folders fe6d818 dl: Add support for new link formats 8296db8 Update README with new build information/git repository link 471a3cf Make doc generator programs optional 9e47ad4 Move to meson buildystem eae38e9 Drop github issue template a9af5d8 Update PGP fingerprint 1f52c9c We need to follow symlinks on local paths in megatools dl 8bbc21c Mark some functions as static 32bd83a Change e-mail/website addresses 2d9fe44 URL-decode links passed to the dl command #310 cc91968 Add --ip-proto and --netif options f19cf39 Add export tool d3e1d58 Add megatools test tool f0bfefd Drop useless ; 7735dd2 Fix grammar: data are -> data is 99dfea8 Avoid new compiler warnings 2c7e2a3 No need to iterate from the beginning when checking for invalid filename 4e5ba97 Replace invalid filename characters with _ instead of a space 1aeb275 Merge branch 'master' of github.com:megous/megatools 956c11a Don't use alloca, avoid unbounded path construction c94277f Merge pull request #428 from 64617/fix-filename c91409c Don't immediately fail on invalid filename 2cc7019 Fixes: 'megatools get', Output to STDOUT not working #423 1012d6e Updated todo 0faba1c Fix megatools rm not removing the child nodes from the fs_nodes 65f8afd Don't free nodes too early #417 a1c945c dl: Download exported files even if root is not a folder #417 2ca921f Merge branch 'master' into x f651ba0 Revert "Support ....?.... URL scheme for single file downloads" d52d0ce Refactor update_pathmap_prune to make it clearer, fix memleaks 7f2dcae Merge pull request #417 from mdirik/file-url c78d0d6 Support ....?.... URL scheme for single file downloads 8758b65 Sort order of downloads #401 8309767 Merge pull request #414 from taiyu-len/master 978e4be Show file sizes in pick_nodes 28a2fbe Drop libmega mentions #412 1ea525e Fix download speed limit not being applied by curl if handle is re-used #380 2b68a46 Fix implicitly declared function warnings in shell.c #406 3c24b71 Add link for the experimental windows builds f218192 Optimize exported folder filesystem parsing #405 0e21e38 Fix megac doc b04961d Fix parameter type to curl_easy_setopt ac07641 Fix for "Hardcoded colors affects readability" #404 2255c5d Allow more retries during download (when over quota) #403 0346e76 Add git warning/build instructions to autogen.sh 548042e Add gc_tool_fini attribute 653ba6c Fix alignment of exported links 26a7a0d Add docbook-xml dependency #391 3459d1f Update issue template 1a7e3f4 Update README file for 1.11.0 70c624e Don't download data when file already exists when using megadl #386 5acf268 Add -std=c99 compiler flag a6df742 Always retry the upload, on any server returned error 80e9767 Drop special handling of EAGAIN 7827235 Report possibly transient failures from curl as HTTP_ERROR_COMM_FAILURE 9c150b3 Bump version to 1.11.0-git 3637555 Drop AC_PROG_RANLIB (libtool is enough) f6dcdf5 Don't be too silent 095d284 Fix contact information d2c5c38 Improve build instructions #384 b304d5c Use libtool to fix static linking d7b9037 Drop option groups a4f638e Fix a couple of typos b95ac5d Updated NEWS/TODO 63771cd Drop dead autoconf code 4437c0c Update megatools docs for the new command naming scheme 852852b Add option to install compat links 6cbd650 Build a single binary instead of a binary for each command 6736622 Drop accent 7ac9569 Bump the year ff0f5f1 We don't use gnutls/glib-networking anymore, drop unnecessary code 07ba1f9 Implement public key pinning for the API server megatools 1.10.2 - 2018-07-31 ============================= This is a bugfix release. This release was sponsored by donations to fix large file downloads and improve download robustness after recent mega.nz API change. Thank you! Fixes: - Actually fix connection dropping when downloading big files Improvements: - Add DNS resolution cache sharing accross all cURL handles - Increase receive buffer size to 256kB megatools 1.10.1 - 2018-07-27 ============================= This is a bugfix release + one feature addition by del1a. New features: - Allow to interactively choose which files to download from a public folder via megadl --choose-files Fixes: - Connection dropping when downloading big files - Compatibility with older libcurl releases - Fix occasional crashes when starting transfer worker threads - Fix clang compatibility megatools 1.10.0 - 2018-07-22 ============================= This release contains new features and optimizaions. New features/optimizations: - Chunked upload using up to 16 concurrent connections. Remember, more is not always better. - Improved upload stability. When mega drops or hangs the data connection, megatools will simply restart the upload of the chunk of data that got lost. - 5x AES enryption/decryption speed increase for uploads and downloads. Now it's possible to max out a gigabit connection on a cheap Intel based VPS. - Improved progress reporting wih a summary of the average speed at the end. - You can use --debug http to see all HTTP requests and connections, for debugging connection isues. - Error 509 is reported with explanation. - Implemented automatic resume for interrupted downloads. Megatools now writes data to a temporary file and renames it to the target name, when the download is fully completed. This way you'll notice that download was interrupted by CTRL+C and megatools will not leave half finished work behind. - Add contributed bash auto-completion file by albaldi #368 - Add support for authenticated downloads of exported files using megadl #298 (So that your account download quota is used instead of public quota.) - Add Upload section and CreatePreviews setting into rc file. Add --enable-previews option. - Add support for megals --print0|-0 Fixes: - Upload hangs at 100% #366 #360 #365 - Don't send tetrminal escape sequences, when redirecting stdout. - Fix comments syntax in megarc man page #359 - Use glib's variants of PRIu64, fixes #328 - megadl doesn't use account session when downloading folder links #304 - Skip symlinked folders when uploading in megacopy #262 - Turn on TCP keepalive probes in CURL #271 - Fix socks proxy support in megareg (it was not enabled correctly) #287 - Fix OpenSSL 1.1 compatibility #263 - Fix compiling against libressl - Fix b64_aes128_cbc_encrypt_str string length handling #242 - Minor docummentation updates/fixes Credits: - Thanks to ERap320, megatools is also available on Chocolatey (see gihub issue #347) (If you want to verify origin of the binaries, because they are not distributed in the officialy signed zip file) - Thanks to christarazi, megatools supports downloading specific sub-nodes of a public folder. (#254) Thanks also go to all other contributors for improving documentation, reporting bugs and testing. megatools 1.9.98 - 2016-11-03 ============================= Bugfix release with some UI imporvements. New features: - Support upload/download speed limit settings - Support socks proxy - Improved progress reporting - Support for OpenSSL 1.1.x Removals: - Remove undocummented --abort-on-error option. Tools now always report errors through exit status. - Remove libmega.so public library support and a lot of unused code that was planned to be used for 2.0 - Remove megamv (it was never implemented and confused users) - Remove megafs (it was just an experiment and confused users) Cleanups: - Cleanup build system a bit - Cleanup CLI option handling, improved --help output Fixes: - Enable automatic decompression (CURLOPT_ACCEPT_ENCODING) (by protomouse) (This finally fixes the problem with HTTP compression.) - Exit status from all the tools is now correctly reported - Fix syncing of symlinked files - Support very long passwords in the password prompt (up to 1024 chars) megatools 1.9.97 - 2016-02-02 ============================= Bugfix release. Fixes: - Mega started compressing HTTP responses to API calls. Megatools now uses libcurl to handle API requests, so HTTP compression is now supproted. megatools 1.9.96 - 2016-01-02 ============================= Bugfix release. Fixes: - Refer to mega.nz and use mega.nz links instead of deprecated mega.co.nz - Fix various build issues megatools 1.9.95 - 2015-04-15 ============================= Bugfix and optimization release. Fixes: - Improve compatibility wit older curl (whatever version is in centos 6.5) - Fix regression in megadl: fails to download exported folders #90 - Remove megamv as it is not and will not be implemented in 1.9 branch - Slow down progress reporting on windows to once per second #102 - Correct the default values in mega_session_load() Improvements: - Rename megasync to megacopy to avoid conflict with official mega sync client - Optimize session loading - issue #100 - Optimize session loading - further optimization - issue #100 - There's no need to use dbus-launcher when running megatools outside running desktop to avoid a warning message. megatools 1.9.94 - 2015-01-02 ============================= Bugfix and optimization release. Fixes: - Force use of IPv4 addresses - Fix path sanitization so that trailing slashes are ignored - Document config file oddities in megarc manpage - Add info on how to build/install megatools on various distros Improvements: - Optimize path handling code so that filesystems with many files are usable. Previously, megatools would hang for quite some time when updating it's path map. - Use non-recursive makefile to build megatools megatools 1.9.93 - 2014-10-24 ============================= Added OpenSSL exemption to the license. Fixes: - megadf now returns correct values - fixed path sanitization in mega_session_stat - raised safety limit os size of POST responses from 32 to 256 MiB - show progress when downloading folder using megadl - optimized buffer allocation during donwload and upload - report details of errors when stream callback fails to read/write file - fix detection of convert executeable for preview generation - allow to disable file previews megatools 1.9.92 - 2014-08-24 ============================= Created stable branch for bugfixes. This is the release from the stable branch. This release contains mostly small bug fixes. Most important change is that Megatools now recognizes server busy errors as reported by mega.nz via HTTP status 500 Server Is Busy, and retries the communication. This reduces command failures by a lot, and you'll not see 500 Server Is Busy errors anymore. Another major news is update to the windows package. There are now two packages for 32bit and 64bit windows platform, and all dlls were updated to their latest versions. Other news: - Added 64bit windows package - Unmask Megatools to Mega.nz (user Megatools/VERSION User-Agent) - Check for a2x if --enable-docs-build=yes - Megadl now reports file download errors - Add megadl --print-names option - Fix megasync error (issue #45 on github) - Fix megadl used with pipe (--path -) on windows (issue #38) - Installation instructions for Mac - Add info about Gentoo ebuild to the README file (issue #26) - Fix Megatools thumbnails for PDF (issue #5) - Do repeated API calls after 500 Server Is Busy and increase retry delay - Fix issue to download a file with accent with megaget (issue #22) megatools 1.9.91 - 2013-04-14 ============================= Please note, that megatools is using GNOME release versioning. That means that in MAJOR.MINOR.PATCH, odd MINOR number signifies development version of the tools. PATCH version is increased on each public release. By using odd MINOR versions of the software you are using an unstable branch. That doesn't mean it will be crashing on you, but it may have certain quirks, and oddities, missing or incomplete features, etc. Next stable release is going to be 2.0.0. To the news: - Added man pages for all tools - Removed megastream in favor of mega{dl,get} --path - - Megadf - fix unsigned overflow when server reports 0 total space - Implemented --debug option - Win32: Drop requirement for manual setup of GIO_EXTRA_MODULES - Load certificates from the binaries folder on windows - Enable CA validation for TLS connections - Fix compilation on FreeBSD by adding --enable-warnings - Add note that on FreeBSD GNU make must be used to build the tools - Fix typedef redefinitions (error on older gcc's) - Fix megals --names logic - megaput/megaget/megadl/megasync: --no-progress support - Fix mega_session_dl streaming mode segfault - megamkdir/megarm: Check for required arguments - Pretty print JSON in debug output AUR package is available at https://aur.archlinux.org/packages/megatools/ Ubunut PPA is availble at https://launchpad.net/~megous/+archive/ppa If someone wants to take over packaging for Ubuntu or Debian, that would be very appretiated, as my speciality is Arch Linux. megatools 1.9.90 - 2013-04-11 ============================= Implemented much simpler and faster json parser than json-glib, which was used before. Megatools now use sjson, which you can find at: https://github.com/megous/sjson Json-glib dependency was thus dropped. Initial work on getting rid of dependency on libcurl. Initial work on the new libmega library with support for gobject-introspection based bindings to various languages: JavaScript, Python, Ruby, LUA,... Experimental implementation of Mega API in JavaScript (gjs), this should help people develop GNOME GUI for Mega in the future. New command line tools: megafs - mount mega.nz filesystem usong FUSE (Linux only) megamv - move and rename remote files and directories megatools 1.2.2 - 2013-04-11 ============================ Bugs fixed: - Trash and Inbox paths are no longer mixed up (bug reported by Ovidiu) - megadl segfault - megaput --path /Root/nonexistent-file local-file (remote file would get the name of the local file, instead of nonexistent-file) - megals /Root/file-name would return nothing - Segfault with password length over 15 chars - Fix file size parsing to make it work with files over 2GB in size megatools 1.2.1 - 2013-03-12 ============================ Added support for OpenSUSE 12.1 and newer versions. Build libmega statically for binary distribution to ease installation outside of /usr/local. megatools 1.2.0 - 2013-03-12 ============================ Megatools now support download of shared files and folders. Megatools support creating thumbnails for image and video files uploaded to Mega. This is enabled only on Linux, as it requires ffmpegthumbnailer and convert (from ImageMagick package). You can now manage your contacts list via: - mkdir /Contacts/some@email.com - rm /Contacts/some.other@email.com Exported folders can be downloaded using megadl. All tools gained a few more options to help with scripting needs. If you don't provide password on command-line or in config file you'll be asked for it when the tool runs. Megals tool gained many new options. OpenSSL locks are now properly initialized. megatools 1.1.1 - 2013-03-10 ============================ Binary packages for linux support more Linux distributions. Supported distros include (both 32bit and 64bit editions): - Ubuntu LTS 12.04.2 and later - Fedora 17 and later - Current Arch Linux - Suse (with a package for ubuntu) - ...and probably any recent distribution with all dependencies installed Package structure was changed to allow easy installation to /usr/local. To install, run: sudo tar -x -f megatools-1.1.1-i686.tar.gz -C / --no-overwrite-dir Tools now use shared library, which you can use to develop your own Mega tools. For example: export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig gcc -o sample sample.c -Wl,-rpath,/usr/local/lib `pkg-config --cflags --libs libmega` Sample source code sample.c can be found in /usr/local/share/doc/megatools/ To develop mega apps, you'll need to install development packages on distros like Fedora or Ubuntu. For example on Fedora: yum install openssl-devel json-glib-devel libcurl-devel glib2-devel Or on Ubuntu: apt-get install libssl-dev libjson-glib-dev libcurl4-openssl-dev gcc make pkg-config megatools 1.1.0 - 2013-03-09 ============================ Megareg tool is now implemented, and can be used to register new accounts without the need for Mega website. Unless you login to Mega, Mega will never be able to get your password key. Tools don't show version banner anymore, unless asked for via --version option. Megadl learned to handle SIGPIPE if server drops connection, and megadl tries to write into a closed socket. megatools 1.0.0 - 2013-03-08 ============================ This is initial public release of megatools. Current features are: - Local caching of remote session/filesystem information for faster execution. Cache is encrypted with your password key. - Support for configuration file to put login information in. This is not wildly secure, but it's convenient. - Initial set of tools to manipulate remote filesystem: megadf Show your cloud storage space usage/quota megals List all remote files megamkdir Create remote directory megarm Remove remote file or directory megaput Upload individual files megaget Download individual files megadl Download file from a "public" Mega link (doesn't require login) megastream Streaming download of a file (can be used to preview videos or music) megasync Upload or download a directory tree See TODO for features that are comming in the future releases. megatools-1.11.3.20250203/README000066400000000000000000000206451474777720000153710ustar00rootroot00000000000000megatools - command line client application for Mega ==================================================== Megatools is a collection of programs for accessing Mega service from a command line of your desktop or server. Megatools allow you to copy individual files as well as entire directory trees to and from the cloud. You can also perform streaming downloads for example to preview videos and audio files, without needing to download the entire file. Megatools are robust and optimized for fast operation - as fast as Mega servers allow. Memory requirements and CPU utilization are kept at minimum. Megatools can upload 70MiB/s and download 80 MiB/s on a cheap single core Intel based VPS. You can register mega account using the 'megatools reg' tool, with the benefit of having true control of your encryption keys, compared to using mega.nz web client. Anything done in the web browser uses a code you don't have control over (unless it's your own website), and thus can't be fully trusted with your password. Mega website can be found at https://mega.nz. Megatools official website is at https://xff.cz/megatools/ Tools ===== reg Register and verify a new mega account df Show your cloud storage space usage/quota ls List all remote files test Test for existence of remote files or folders export Create public links for remote files mkdir Create remote directory rm Remove remote file or directory put Upload individual files get Download individual files dl Download file from a "public" Mega link (doesn't require login) copy Upload or download a directory tree All of these tools do: - Local caching of remote session/filesystem information for faster execution. Cache is encrypted with your password key. - Support loading login credentials from a configuration file. Usage ===== See the man pages for how to use individual tools: man megatools Man pages are also available online at: https://xff.cz/megatools/man/megatools.html Installation on Windows ======================= Official builds for 32bit and 64bit Windows are avaialbe at: https://xff.cz/megatools/ https://xff.cz/megatools/builds/experimental/ Megatools is also available on Windows via Chocolatey thanks to ERap320. See: https://chocolatey.org/packages/megatools/ You can contact the chockolatey package maintainer here: https://github.com/megous/megatools/issues/347 Installation on macOS ===================== Thanks to Carl Moden, megatools is available in Homebrew (http://brew.sh/). You can therefore install megatools with: brew install megatools Installation on your favorite GNU/Linux distribution ==================================================== Megatools may already be pre-packaged in the package repository of your distribution. It is already available at least in: - Arch Linux (AUR) - https://aur.archlinux.org/packages/megatools/ - Debian - https://packages.debian.org/sid/megatools - Fedora - https://admin.fedoraproject.org/pkgdb/package/rpms/megatools/ - Gentoo - https://packages.gentoo.org/packages/net-misc/megatools - openSUSE - https://software.opensuse.org/package/megatools - Ubuntu - https://packages.ubuntu.com/cosmic/megatools Be sure to check your distribution's package repository first. Installation on FreeBSD ======================= Megatools is available in ports thanks to Maxim V. Kostikov: https://www.freshports.org/net/megatools/ Using a static build for Linux ============================== Experimental static build is available since version 1.11.0. This build provides a single megatools binary that can be copied to any GNU/Linux distribution and run from there. It is reported that you can even copy an ARM build to your Android smartphone and run it from your phone. This build is useful if you want to avoid the hassle of bulding megatools on old distributions like older versions of CentOS or RedHat. Static builds are available here: https://xff.cz/megatools/builds/experimental/ Building megatools from source code =================================== The official source code tarball is available at: http://xff.cz/megatools/builds/ You should check that the code was released by me by verifying PGP signatures provided alongside the code. You will need to install a few dependnencies before you can build megatools from source code. Package names of these dependencies differ depending on your GNU/Linux distribution. Runtime dependencies are: glib2, libcurl and openssl Build time dependencies are: gcc, make, pkg-config (pkgconf) On Debian, Ubuntu: apt-get -y install build-essential libglib2.0-dev libssl-dev \ libcurl4-openssl-dev If you want to build man pages, too, you need to: apt install asciidoc docbook2x --no-install-recommends or disable the build of man pages via meson -Dman=false option. On Fedora and CentOS: yum -y install gcc make glib2-devel libcurl-devel openssl-devel On OpenSUSE: zypper -n install gcc make glib2-devel libcurl-devel openssl-devel On Arch Linux: pacman -Sy --noconfirm --needed pkgconf gcc make glib2 curl On Alpine Linux: apk add --update build-base libcurl curl-dev asciidoc openssl-dev glib-dev \ glib libtool automake autoconf WARNING: Newer versions of megatools use meson build system. Build steps are different for meson. You can build megatools into your HOME directory, so that they'll not pollute your /usr or /usr/local by using --prefix $HOME/.local option to meson. Example build steps: wget https://xff.cz/megatools/builds/megatools-1.11.0.tar.gz{,.asc} gpg --verify megatools-1.11.0.tar.gz.asc tar xf megatools-1.11.0.tar.gz cd megatools-1.11.0 meson setup --prefix $HOME/.local .build ninja -C .build ninja -C .build install If you encounter issues, read the error messages carefully. They may contain hints on how you can solve the issue yourself (missing dependencies, missing C compiler build flags, etc.). Now you can run megatools from ~/.local/bin. export PATH="$HOME/.local/bin:$PATH" megatools ls Building megatools from git repository and contributing code ============================================================ Megatools use meson build system. See https://mesonbuild.com/ Build steps for meson are: git clone https://xff.cz/git/megatools cd megatools meson setup .build ninja -C .build sudo ninja -C .build install You may need to install dependencies listed above in the previous section. Third party tools/scripts ========================= Megatools are meant as a low-level tools that can be used as a base to create more complicated tools for working with mega.nz. Other people have created a third party scripts on top of megatools. If you're one of them and want to have your project listed here, let me know. Some third party tools can be found at: https://amourspirit.github.io/mega_scripts/ Author ====== Megatools were written by Ondřej Jirman , 2013-2022 My PGP key can be found at: https://xff.cz/key.txt (Fingerprint is: EBFBDDE11FB918D44D1F56C1F9F0A873BE9777ED) Official website is: https://xff.cz/megatools/ Contributors ============ - Alberto Garcia - aurelien - bAndie91 - Chris Tarazi - cyrozap - Dal1a - David Guillen Fandos - Erik Nordstrøm - ivesen - Johnathan Jenkins - Kagami Hiiragi - Matthew Schultz - Mert Dirik - Michael Ledin - Michael Ripley - nyuszika7h - Palmer Dabbelt - protomouse - RealDolos - strupo - taiyu - Tom Maneiro - Viktor (Icon) VAD - wdlkmpx Support and bug reports ======================= There is no support for megatools. I only accept patches and maintain reasonably uptodate static builds. You can send patches to megatools@xff.cz if you want them integrated into the main code base that may end up in Linux distributions. License ======= Megatools are licensed under GPLv2 with OpenSSL exemption, see LICENSE file for details. This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/) megatools-1.11.3.20250203/TODO000066400000000000000000000050271474777720000151760ustar00rootroot00000000000000- bug: when the session is started by megatools dl, it will have an empty fs_nodes list, so next call to megatools ls will return empty results and user needs to do --reload - add usages to the code --------------------------------------- - https://github.com/megous/megatools/issues/306 Use XDG_CONFIG_HOME for configuration file #306 - https://github.com/megous/megatools/issues/305 megals return values when secifying invalid paths - https://github.com/megous/megatools/issues/292 Create remote folder if it doesn't exist when using megacopy #292 - Output coloring doesn't work on non-utf8 Linux #270 My system is using the fi_FI@euro locale, which uses the ISO-8859-15 encoding. - https://github.com/megous/megatools/issues/269 - key pinning - Add time left and download speed #239 - https://github.com/megous/megatools/issues/163 - Prompt for decryption key when not avaliable in megadl #163 - megacopy: show paths relative to destination directory https://github.com/megous/megatools/issues/120 - Make remote path optional for megacopy #307 - https://github.com/megous/megatools/issues/322 --force option - https://github.com/megous/megatools/issues/372 improve output - gpg/ssh agent/windows vault support #include #include #include #pragma hdrstop void main () { { //--- SAVE char* password = "brillant"; DWORD cbCreds = 1 + strlen(password); CREDENTIALW cred = {0}; cred.Type = CRED_TYPE_GENERIC; cred.TargetName = L"FOO/account"; cred.CredentialBlobSize = cbCreds; cred.CredentialBlob = (LPBYTE) password; cred.Persist = CRED_PERSIST_LOCAL_MACHINE; cred.UserName = L"paula"; BOOL ok = ::CredWriteW (&cred, 0); wprintf (L"CredWrite() - errno %d\n", ok ? 0 : ::GetLastError()); if (!ok) exit(1); } { //--- RETRIEVE PCREDENTIALW pcred; BOOL ok = ::CredReadW (L"FOO/account", CRED_TYPE_GENERIC, 0, &pcred); wprintf (L"CredRead() - errno %d\n", ok ? 0 : ::GetLastError()); if (!ok) exit(1); wprintf (L"Read username = '%s', password='%S' (%d bytes)\n", pcred->UserName, (char*)pcred->CredentialBlob, pcred->CredentialBlobSize); // must free memory allocated by CredRead()! ::CredFree (pcred); } } - only retry on non-persistent errors - rework streaming (split download function into a streaming core and a wrapper) ------ - better abort mechanism for http workers - https://github.com/megous/megatools/issues/324 megatools-1.11.3.20250203/contrib/000077500000000000000000000000001474777720000161425ustar00rootroot00000000000000megatools-1.11.3.20250203/contrib/bash-completion/000077500000000000000000000000001474777720000212265ustar00rootroot00000000000000megatools-1.11.3.20250203/contrib/bash-completion/megatools000066400000000000000000000232141474777720000231450ustar00rootroot00000000000000# Copyright (c) 2017 albaldi # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # See here for updates: https://github.com/megous/megatools/issues/368 _remotepath() { local flags_megals cur listing i tmp cur="${COMP_WORDS[COMP_CWORD]}" # workaround for when cur is empty and expand to '/ ' instead of /Root,/Contacts, etc if [[ -z "${cur}" ]] ; then tmp="/" else tmp="${cur%/*}/" fi flags_megals=() for i in "${!COMP_WORDS[@]}" do case "${COMP_WORDS[i]}" in --config) flags_megals+=("${COMP_WORDS[i]}") flags_megals+=("$(compgen -W "${COMP_WORDS[i+1]}")") i=$((i+2)) continue ;; --proxy) flags_megals+=("${COMP_WORDS[i]}") flags_megals+=("$(compgen -W "${COMP_WORDS[i+1]}")") i=$((i+2)) continue ;; --username|-u) flags_megals+=("${COMP_WORDS[i]}") flags_megals+=("$(compgen -W "${COMP_WORDS[i+1]}")") i=$((i+2)) continue ;; --password|-p) flags_megals+=("${COMP_WORDS[i]}") flags_megals+=("$(compgen -W "${COMP_WORDS[i+1]}")") i=$((i+2)) continue ;; *) ;; esac done # awesome https://stackoverflow.com/a/26511572 mapfile -t -d '' listing < <( megals --print0 "${flags_megals[@]}" "$tmp" ) mangle() { printf '%q ' "${@}" } mapfile -t COMPREPLY < <( compgen -W "$(mangle "${listing[@]}")" -- "${cur}" ) # https://stackoverflow.com/a/11536437 # COMPREPLY=($(printf "%q\\n" "${COMPREPLY[@]}")) } _megatools() { local cur prev opts_megacopy opts_megadl opts_megals opts_megaput opts_megarm opts_megadf opts_megaget opts_megamkdir opts_megareg opts_debug cmd COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" cmd="${COMP_WORDS[0]}" #for i in megacopy megadl megals megaput megarm megadf megaget megamkdir megareg ; do printf "opts_%s=\"%s\"\n" "$i" "$("$i" --help-all | grep -o -- "-[^,= ]\{1,\}" | tr '\n' ' ')" ; done opts_megacopy="-h --help --help-all --help-basic --help-network --help-auth --help-upload --help-download --config --ignore-config-file --debug --version --limit-speed --proxy -u --username -p --password --no-ask-password --reload --enable-previews --disable-previews --disable-resume -r --remote -l --local -d --download --no-progress --no-follow -n --dryrun" opts_megadl="-h --help --help-all --help-basic --help-network --help-auth --help-download --config --ignore-config-file --debug --version --limit-speed --proxy -u --username -p --password --no-ask-password --reload --disable-resume --path --no-progress --print-names" opts_megals="-? --help --help-all --help-basic --help-network --help-auth --config --ignore-config-file --debug --version --limit-speed --proxy -u --username -p --password --no-ask-password --reload -n --names -R --recursive -l --long --header -h --human -0 --print0 -e --export" opts_megaput="-h --help --help-all --help-basic --help-network --help-auth --help-upload --config --ignore-config-file --debug --version --limit-speed --proxy -u --username -p --password --no-ask-password --reload --enable-previews --disable-previews --path --no-progress" opts_megarm="-h --help --help-all --help-basic --help-network --help-auth --config --ignore-config-file --debug --version --limit-speed --proxy -u --username -p --password --no-ask-password --reload" opts_megadf="-? --help --help-all --help-basic --help-network --help-auth --config --ignore-config-file --debug --version --limit-speed --proxy -u --username -p --password --no-ask-password --reload -h --human --mb --gb --total --used --free" opts_megaget="-h --help --help-all --help-basic --help-network --help-auth --help-download --config --ignore-config-file --debug --version --limit-speed --proxy -u --username -p --password --no-ask-password --reload --disable-resume --path --no-progress" opts_megamkdir="-h --help --help-all --help-basic --help-network --help-auth --config --ignore-config-file --debug --version --limit-speed --proxy -u --username -p --password --no-ask-password --reload" opts_megareg="-h --help --help-all --help-basic --help-network --config --ignore-config-file --debug --version --limit-speed --proxy -n --name -e --email -p --password --register --verify --scripted --register" opts_debug="http api fs cache tman" case "${prev}" in --config) _filedir return 0 ;; --local|-l) _cd return 0 ;; --debug) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_debug}" -- "${cur}") ) return 0 ;; --path) if [[ "${cmd}" = "megaput" ]] ; then _remotepath return 0 fi _filedir return 0 ;; --remote|-r) _remotepath return 0 ;; *) ;; esac if [[ ${cur} == -* ]] ; then case "${cmd}" in megacopy) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_megacopy}" -- "${cur}") ) return 0 ;; megadl) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_megadl}" -- "${cur}") ) return 0 ;; megals) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_megals}" -- "${cur}") ) return 0 ;; megaput) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_megaput}" -- "${cur}") ) return 0 ;; megarm) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_megarm}" -- "${cur}") ) return 0 ;; megadf) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_megadf}" -- "${cur}") ) return 0 ;; megaget) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_megaget}" -- "${cur}") ) return 0 ;; megamkdir) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_megamkdir}" -- "${cur}") ) return 0 ;; megareg) # shellcheck disable=SC2207 COMPREPLY=( $(compgen -W "${opts_megareg}" -- "${cur}") ) return 0 ;; *) ;; esac return 0 fi case "${cmd}" in megamkdir|megarm|megaget|megals) _remotepath ;; *) _filedir ;; esac } # because of _remotpath I needed to add -o filenames, if not _remotepath suggestions don't get quote/escaped for space and similar characters # solution found reading https://github.com/jgm/pandoc/issues/2749 complete -o filenames -F _megatools megacopy megadl megals megaput megarm megadf megaget megamkdir megareg # /* vim: set filetype=sh ts=8: */ megatools-1.11.3.20250203/docs/000077500000000000000000000000001474777720000154325ustar00rootroot00000000000000megatools-1.11.3.20250203/docs/asciidoc.conf000066400000000000000000000016131474777720000200600ustar00rootroot00000000000000## man: macro # # Usage: man:command[manpage-section] # # Note, {0} is the manpage section, while {target} is the command. # # Show man link as: (
); if section is defined, else just show # the command. [macros] (?su)[\\]?(?Pman):(?P\S*?)\[(?P.*?)\]= ifdef::backend-docbook[] ifdef::doctype-manpage[] [man-inlinemacro] {0%{target}} {0#} {0#{target}{0}} {0#} [header] template::[header-declarations] {mantitle} {manvolnum} {manname} {manpurpose} endif::doctype-manpage[] ifdef::doctype-article[] [man-inlinemacro] {target}{0?({0})} endif::doctype-article[] endif::backend-docbook[] megatools-1.11.3.20250203/docs/auth-options.txt000066400000000000000000000003311474777720000206220ustar00rootroot00000000000000-u :: --username :: Account username (email) -p :: --password :: Account password --no-ask-password:: Never ask interactively for a password --reload:: Reload filesystem cache megatools-1.11.3.20250203/docs/basic-options.txt000066400000000000000000000012221474777720000207420ustar00rootroot00000000000000include::network-options.txt[] --config :: Load configuration from a file --ignore-config-file:: Disable loading .megarc --debug []:: Enable debugging of various aspects of the megatools operation. You may enable multiple debugging options separated by commas. (eg. `--debug api,fs`) + Available options are: * `http`: Dump HTTP request/response details (can be used to debug connection/proxy issues) * `api`: Dump Mega.nz API calls * `fs`: Dump Mega.nz filesystem (may require `--reload` to actually print something) * `cache`: Dump cache contents * `tman`: Dump transfer manager events --version:: Show version information megatools-1.11.3.20250203/docs/download-options.txt000066400000000000000000000001421474777720000214700ustar00rootroot00000000000000--disable-resume:: Don't resume downloads from partially downloaded file. Default is to resume. megatools-1.11.3.20250203/docs/footer.txt000066400000000000000000000011771474777720000174770ustar00rootroot00000000000000SEE ALSO -------- man:megatools[1], man:megarc[5], man:megatools-df[1], man:megatools-dl[1], man:megatools-get[1], man:megatools-ls[1], man:megatools-mkdir[1], man:megatools-put[1], man:megatools-reg[1], man:megatools-rm[1], man:megatools-copy[1]. MEGATOOLS --------- Part of the man:megatools[1] suite of commands. BUGS ---- There is no upstream support for bugreports and feature requests. But you can send code patches to megatools@xff.cz to get them integrated into upstream repository. AUTHOR ------ Megatools was written by Ondrej Jirman , 2013-2022. Official website is link:https://xff.cz/megatools/[]. megatools-1.11.3.20250203/docs/megarc.txt000066400000000000000000000057231474777720000174400ustar00rootroot00000000000000megarc(5) ========= NAME ---- megarc - Configuration file for megatools SYNOPSIS -------- [verse] '~/.megarc' './.megarc' DESCRIPTION ----------- Megatools use INI configuration file to store commonly used login credentials. This makes it less bothersome to use the tools, as you can simply write -------- $ megatools df -------- instead of -------- $ megatools df --username my@email.com --password mypass -------- when using the tools. Configuration file is read either from the current directory or user's home directory. Exceptions are when `--ignore-config-file` was passed to the tool, or when explicit path to the config file was given via `--config `. CONFIG FILE SECTIONS -------------------- All sections are optional. All names are case sensitive, thus you must write [Login], and not [login], and so on. [Login] Section ~~~~~~~~~~~~~~~ Username:: Your username. Password:: Your password. Be aware that back slashes have special meaning. If you have back slash in your password, you must escape it with another backslash. For example: my\nice\password would be written as my\\nice\\password in the config file. [Cache] Section ~~~~~~~~~~~~~~~ Timeout:: Cache timeout in seconds (default is 10 minutes). [Network] Section ~~~~~~~~~~~~~~~~~ DownloadSpeedLimit:: Set maximum allowed download speed in KiB/s. 0 means no limit. Overrides SpeedLimit setting. UploadSpeedLimit:: Set maximum allowed upload speed in KiB/s. 0 means no limit. Overrides SpeedLimit setting. When using ParallelTransfers > 1, upload speed limit is applied to each transfer individually. SpeedLimit:: Set maximum allowed upload and download speed in KiB/s. 0 means no limit. ParallelTransfers:: Set maximum allowed number of parallel connections when upload or downloading a file. The file is split into chunks of a size between 128 kiB and 1 MiB, and these chunks are uploaded in parallel. The number must be between 1 and 16. Default is 5. Proxy:: Use proxy server to connect to mega.nz. More information can be found in libcurl documentation at link:https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html[]. Some acceptable values are: * `socks5://localhost:9050` : Local SOCKSv5 proxy server * `socks5h://localhost:9050` : Local SOCKSv5 proxy server with DNS handled by the proxy [Upload] Section ~~~~~~~~~~~~~~~~ CreatePreviews:: Create Previews (see --enable-previews option). [UI] Section ~~~~~~~~~~~~ Colors:: Enable color output in the progress reporting. Colors are disabled by default to support all kinds of terminal configurations. Colors are not configurable, yet. EXAMPLE ------- Create ~/.megarc (on linux) or mega.ini file containing these 3 lines: ------------ [Login] Username = your@email Password = yourpassword [Network] # 1MiB/s SpeedLimit = 1024 # Use over TOR Proxy = socks5://127.0.0.1:9050 ParallelTransfers = 2 [UI] Colors = true ------------ Run man:megatools-df[1] to check that megatools are able to login to your account. include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-copy.txt000066400000000000000000000024431474777720000211400ustar00rootroot00000000000000megatools-copy(1) ================= NAME ---- megatools copy - Upload/download entire directories to/from your Mega.nz account SYNOPSIS -------- [verse] 'megatools copy' [-n] [--no-progress] --local --remote 'megatools copy' [-n] [--no-progress] --download --local --remote DESCRIPTION ----------- Sync remote and local directories. No files are ever overwritten or removed. Default direction is to upload files to the cloud. If you want to download files, you have to add `--download` option. OPTIONS ------- -r :: --remote :: Remote directory path. -l :: --local :: Local directory path. -d:: --download:: Download files from the Mega.nz. The default is to upload. -n:: --dryrun:: Don't perform any actual changes, just print what would be done. --no-progress:: Disable upload progress reporting. include::upload-options.txt[] include::download-options.txt[] include::auth-options.txt[] include::basic-options.txt[] EXAMPLES -------- * Upload directory. + ------------ $ megatools copy --local MyBackups --remote /Root/Backups ------------ * Download directory. + ------------ $ megatools copy --local MyBackups --remote /Root/Backups --download ------------ include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-df.txt000066400000000000000000000020651474777720000205570ustar00rootroot00000000000000megatools-df(1) =============== NAME ---- megatools df - Show total available, used, or free space in the cloud SYNOPSIS -------- [verse] 'megatools df' [--free|--total|--used] [--mb|--gb|-h] DESCRIPTION ----------- Shows available, used and free space in the cloud in machine or human readable formats. OPTIONS ------- --total:: Show only total available space (free + used). --free:: Show only free space. --used:: Show only used space. --human:: -h:: Display file sizes in a human readable format. --mb:: Show in MiB units. --gb:: Show in GiB units. include::auth-options.txt[] include::basic-options.txt[] EXAMPLES -------- * Show overall human readable space usage information: + ------------ $ megatools df -h Total: 50.0 GiB Used: 6.4 GiB Free: 43.6 GiB ------------ * Check free space from a script: + ------------ $ test `megatools df --free --gb` -lt 1 && \ echo "You have less than 1 GiB of available free space" ------------ KNOWN BUGS ---------- Megadf can't determine available space for premium accounts. include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-dl.txt000066400000000000000000000033241474777720000205640ustar00rootroot00000000000000megatools-dl(1) =============== NAME ---- megatools dl - download exported files and directories from Mega.nz SYNOPSIS -------- [verse] 'megatools dl' [--no-progress] [--path ] ... 'megatools dl' --path - DESCRIPTION ----------- Downloads exported files and folders from Mega.nz. Handles links like: * https://mega.nz/#!7YVWhCzZ!bauBlAkKKvv8hIm-8-qFmGOYS289ToQWN7rGFPzXB_w * https://mega.nz/#F!HIlFDajT!HLiVvQQkSe1d0ogxZuaOJg When downloading individual files, these are placed into ``. When downloading folders, the contents of the folder are placed into directory specified by ``. To export files, you can use Mega.nz web application, or man:megatools-ls[1]'s `--export` option. OPTIONS ------- --path :: Local directory to download to. Defaults to the current working directory. + If `` is `-`, remote file will be streamed to stdout. --no-progress:: Disable download progress reporting. This is implied when streaming. --print-names:: Print names/paths of successfully downloaded files (one per line). --choose-files:: Print all files in a shared folder and choose which files or subfolders to download. When choosing a subfolder, the entire subfolder will be downloaded. include::download-options.txt[] include::basic-options.txt[] :: File and folder links to download from. :: Link to exported file to stream. EXAMPLES -------- * Download exported file: + ------------ $ megatools dl 'https://mega.nz/#!7YVWhCzZ!bauBlAkKKvv8hIm-8-qFmGOYS289ToQWN7rGFPzXB_w' ------------ * Download exported folder: + ------------ $ megatools dl 'https://mega.nz/#F!HIlFDajT!HLiVvQQkSe1d0ogxZuaOJg' ------------ include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-export.txt000066400000000000000000000013101474777720000214770ustar00rootroot00000000000000megatools-export(1) ================= NAME ---- megatools export - create public links for remote files SYNOPSIS -------- [verse] 'megatools export' ... DESCRIPTION ----------- Exports (creates public links) for all files stored in your mega.nz account referenced by ``. Works only for individual files for now. OPTIONS ------- include::auth-options.txt[] include::basic-options.txt[] :: One or more remote filesystem file paths to export. EXAMPLES -------- * Export a file: + ------------ $ megatools export /Root/README https://mega.nz/#!OFFRlbgR!k5rWmLp3mxB0gsq07Ii67PLd9L0wq4KondFLDlfH3Uw ------------ include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-get.txt000066400000000000000000000023551474777720000207470ustar00rootroot00000000000000megatools-get(1) ================ NAME ---- megatools get - download files from your Mega.nz account SYNOPSIS -------- [verse] 'megatools get' [--no-progress] [--path ] ... 'megatools get' --path - DESCRIPTION ----------- Downloads files from your Mega.nz account. *NOTE*: If you want to download entire directories, use man:megatools-copy[1]. OPTIONS ------- --path :: Local path to download to. If this path is a directory, files are placed into the directory. If this path doesn't exist, and it's parent directory does, the file will be downloaded to a specified file (this only works if you specify exactly one remote path). + If `` is `-`, remote file will be streamed to stdout. --no-progress:: Disable download progress reporting. This is implied when streaming. include::download-options.txt[] include::auth-options.txt[] include::basic-options.txt[] :: One or more remote files to download. :: Remote path to a single file to stream. EXAMPLES -------- * Download file to the current directory: + ------------ $ megatools ls /Root /Root /Root/README $ megatools get /Root/README $ ls README ------------ include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-ls.txt000066400000000000000000000054601474777720000206060ustar00rootroot00000000000000megatools-ls(1) =============== NAME ---- megatools ls - List files stored in the cloud SYNOPSIS -------- [verse] 'megatools ls' [-e] [-h] [--header] [-l] [-R] [-n] [...] DESCRIPTION ----------- Lists files stored on Mega.nz, exports public download links. OPTIONS ------- --export:: -e:: For all files that are going to be listed, also display public download link with file key. + *NOTE*: Folders export doesn't work yet. --human:: -h:: Display file sizes in a human readable format. --header:: For long list format, display header describing all listed columns. --long:: -l:: List additional information about listed filesystem nodes. Node handle, owner, node type, file size, and the last modification date. --recursive:: -R:: List directories recursively. This is the default if no paths are specified. --names:: -n:: Show only names of nodes within the directory. This option has effect only if you specified a single path on a command line. --print0:: -0:: Separate file names with NULs instead of new lines. include::auth-options.txt[] include::basic-options.txt[] :: One or more remote filesystem paths to list. If path points to a directory, contents of the directory and the directory itself is listed. When `--names` is given, only the contents of the directory is listed. If path points to a file, the file itself is listed. If omitted, the entire remote filesystem is listed recursively. EXAMPLES -------- * List all files: + ------------ $ megatools ls /Contacts /Inbox /Root /Root/README /Root/bigfile /Trash ------------ * List all files in the /Root, recursively and with details: + ------------ $ megatools ls -l /Root 3RsS2QwJ 2 - 2013-01-22 12:31:06 /Root 2FFSiaKZ Xz2tWWB5Dmo 0 2686 2013-04-15 08:33:47 /Root/README udtDgR7I Xz2tWWB5Dmo 0 4405067776 2013-04-10 19:16:02 /Root/bigfile ------------ * List all files in the /Root, recursively and with details, show only file names: + ------------ $ megatools ls -ln /Root 2FFSiaKZ Xz2tWWB5Dmo 0 2686 2013-04-15 08:33:47 README udtDgR7I Xz2tWWB5Dmo 0 4405067776 2013-04-10 19:16:02 bigfile ------------ * Export download links: + ------------ $ megatools ls -e /Root/README https://mega.nz/#!OFFRlbgR!k5rWmLp3mxB0gsq07Ii67PLd9L0wq4KondFLDlfH3Uw /Root/README ------------ * List files in a more human readable format: + ------------ $ megatools ls -hnl --header /Root/README =================================================================================== Handle Owner T Size Mod. Date Filename =================================================================================== 2FFSiaKZ Xz2tWWB5Dmo 0 2.6 KiB 2013-04-15 08:33:47 README ------------ include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-mkdir.txt000066400000000000000000000016251474777720000212750ustar00rootroot00000000000000megatools-mkdir(1) ================== NAME ---- megatools mkdir - Create remote folder under your Mega.nz account SYNOPSIS -------- [verse] 'megatools mkdir' ... 'megatools mkdir' /Contacts/ DESCRIPTION ----------- Creates folders on Mega.nz. As a special case, by creating a new folder under /Contacts, you're adding a `` to your contacts list. OPTIONS ------- include::auth-options.txt[] include::basic-options.txt[] :: One or more remote directories to create. :: Valid email address of a contact you want to add. EXAMPLES -------- * Create new folder: + ------------ $ megatools mkdir /Root/MyNewFolder $ megatools ls /Root /Root /Root/MyNewFolder ------------ * Add new contact to your contacts list: + ------------ $ megatools mkdir /Contacts/some@email.com ------------ include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-put.txt000066400000000000000000000022321474777720000207720ustar00rootroot00000000000000megatools-put(1) ================ NAME ---- megatools put - upload files to your Mega.nz account SYNOPSIS -------- [verse] 'megatools put' [--no-progress] [--path ] ... DESCRIPTION ----------- Uploads files to your Mega.nz account. *NOTE*: If you want to upload entire directories, use man:megatools-copy[1]. OPTIONS ------- --path :: Remote path to upload to. If this path is a directory, files are placed into the directory. If this path doesn't exist, and it's parent directory does, the file will be uploaded to a specified path (this only works if you specify exactly one file). --no-progress:: Disable upload progress reporting. include::upload-options.txt[] include::auth-options.txt[] include::basic-options.txt[] :: One or more local files to upload. EXAMPLES -------- * Upload file to the /Root: + ------------ $ megatools put README $ megatools ls /Root /Root /Root/README ------------ * Upload file, while naming it differently: + ------------ $ megatools put --path /Root/README.TXT README $ megatools ls /Root /Root /Root/README.TXT ------------ include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-reg.txt000066400000000000000000000040131474777720000207360ustar00rootroot00000000000000megatools-reg(1) ================ NAME ---- megatools reg - Register new Mega.nz account SYNOPSIS -------- [verse] 'megatools reg' [--scripted] --register --email --name --password 'megatools reg' [--scripted] --verify DESCRIPTION ----------- Registers new Mega.nz account. Registration is split into two steps: . `--register`: Creates a new non-verified account . `--verify`: Verifies account with the link that was sent to `` See example of registration below. OPTIONS ------- --register:: Create new non-verified account. --verify :: Verify account previously created by `megatools reg --register`. You need to pass `` that was returned from `megatools reg --register` and a verification link from the email that was sent to ``. --email :: Email serves as your new account username, that you'll be using to sign in. + *NOTES*: Beware that ATM, email format is not validated by megatools. --name :: Your real (or fake) name. --password :: Plaintext password. No strength checking is done, so make sure you pick a strong password yourself. --scripted:: After registration, print the command that can be used to finish the registration in a format suitable for scripting purposes. include::basic-options.txt[] EXAMPLE ------- First create an non-verified account: ------------ $ megatools reg --register --email your@email.com --name "Your Name" --password "Your Password" Registration email was sent to test@megous.com. To complete registration, you must run: megatools reg --verify vbFFFv7AFM25etzkFXLs9A==:Z7FFbgAAAAAAAAAArL33eA==:inEONh3tmwY @LINK@ (Where @LINK@ is registration link from the 'MEGA Signup' email) ------------ Now wait for a verification mail and run the command as asked: ------------ $ megatools reg --verify vbFFFv7AFM25etzkFXLs9A==:Z7FFbgAAAAAAAAAArL33eA==:inEONh3tmwY \ 'https://mega.nz/#confirmciyfWXRGFNcM...' Account registered successfully! ------------ include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-rm.txt000066400000000000000000000020161474777720000206000ustar00rootroot00000000000000megatools-rm(1) =============== NAME ---- megatools rm - Remove files and folders from your Mega.nz account SYNOPSIS -------- [verse] 'megatools rm' ... 'megatools rm' /Contacts/ DESCRIPTION ----------- Removes files and folders from your Mega.nz account. *NOTE*: This command removes folders recursively without asking. Be careful. As a special case, by removing a folder under /Contacts, you're removing a `` from your contacts list. OPTIONS ------- include::auth-options.txt[] include::basic-options.txt[] :: One or more remote files or directories to remove. :: Valid email address of a contact you want to remove. EXAMPLES -------- * Remove a folder: + ------------ $ megatools ls /Root /Root /Root/MyNewFolder $ megatools rm /Root/MyNewFolder $ megatools ls /Root /Root ------------ * Remove contact from your contacts list: + ------------ $ megatools rm /Contacts/some@email.com ------------ include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/megatools-test.txt000066400000000000000000000024061474777720000211440ustar00rootroot00000000000000megatools-test(1) ================= NAME ---- megatools test - Test for existence of files or folders SYNOPSIS -------- [verse] 'megatools test' [-f|-d] ... DESCRIPTION ----------- Tests for existence of files or paths of files stored on Mega.nz. If all `` match the condition, the tool returns status code 0. If one of the `` doesn't exist, the tool returns 1. If one of the `` doesn't match the `-f` or `-d` filter, the tool returns 2. For other errors, the tool returns 3. OPTIONS ------- --file:: -f:: Require all `` to be files. --folder:: -d:: Require all `` to be folders. include::auth-options.txt[] include::basic-options.txt[] :: One or more remote filesystem paths to test. EXAMPLES -------- * Test for existence of a path: + ------------ $ megatools test /Root/README $ test $? -eq 0 && echo "File or folder exists at /Root/README" File or folder exists at /Root/README ------------ * Test for existence of multiple files: + ------------ $ megatools test -f /Root/file1 /Root/file2 $ test $? -eq 0 && echo "/Root/file1 and /Root/file2 are both files" /Root/file1 and /Root/file2 are both files ------------ include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/megatools.txt000066400000000000000000000057171474777720000201770ustar00rootroot00000000000000megatools(1) ============ NAME ---- megatools - Mega.nz command line tools SYNOPSIS -------- [verse] 'megatools df' [--free|--total|--used] [--mb|--gb|-h] 'megatools ls' [-e] [-h] [--header] [-l] [-R] [-n] [...] 'megatools test' [-f|-d] ... 'megatools export' ... 'megatools put' [--no-progress] [--path ] ... 'megatools mkdir' ... 'megatools mkdir' /Contacts/ 'megatools get' [--no-progress] [--path ] ... 'megatools get' --path - 'megatools copy' [-n] [--no-progress] --local --remote 'megatools copy' [-n] [--no-progress] --download --local --remote 'megatools rm' ... 'megatools rm' /Contacts/ 'megatools dl' [--no-progress] [--path ] ... 'megatools dl' --path - 'megatools reg' [--scripted] --register --email --name --password 'megatools reg' [--scripted] --verify DESCRIPTION ----------- Megatools is a collection of programs for accessing Mega service from a command line of your desktop or server. Megatools allow you to copy individual files as well as entire directory trees to and from the cloud. You can also perform streaming downloads for example to preview videos and audio files, without needing to download the entire file. Megatools are robust and optimized for fast operation - as fast as Mega servers allow. Memory requirements and CPU utilization are kept at minimum. You can register account using a man:megatools-reg[1] tool, with the benefit of having true control of your encryption keys. Mega website can be found at https://mega.nz. Megatools can be downloaded at https://xff.cz/megatools/ TOOLS OVERVIEW -------------- man:megatools-reg[1]:: Register and verify a new mega account man:megatools-df[1]:: Show your cloud storage space usage/quota man:megatools-ls[1]:: List all remote files man:megatools-test[1]:: Test for existence of files or folders man:megatools-export[1]:: Create public links for remote files man:megatools-mkdir[1]:: Create remote directory man:megatools-rm[1]:: Remove remote file or directory man:megatools-put[1]:: Upload individual files man:megatools-get[1]:: Download individual files man:megatools-dl[1]:: Download file from a "public" Mega link (doesn't require login) man:megatools-copy[1]:: Upload or download a directory tree CONFIGURATION FILES ------------------- See man:megarc[5] for information about mega configuration file. Each of the individual tools have help that can be accessed using --help parameter. SESSION CACHE ------------- If you modify cloud filesystem from the Mega.nz website or from another computer, you'll need to refresh your session cache. This can be done by using the --reload option to any tool, or by waiting for a cache timeout (default timeout is set to 10 minutes). include::remote-paths.txt[] include::footer.txt[] megatools-1.11.3.20250203/docs/network-options.txt000066400000000000000000000021671474777720000213630ustar00rootroot00000000000000--limit-speed :: Set maximum allowed upload and download speed in KiB/s. This option overrides config file settings. 0 means no limit. When using ParallelTransfers > 1, upload speed limit is applied to each transfer individually. --proxy :: Use proxy server to connect to mega.nz. This option overrides config file settings. More information can be found in libcurl documentation at link:https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html[]. Some acceptable values are: * `none` : Disable proxy if it was enabled in the config file. * `socks5://localhost:9050` : Local SOCKSv5 proxy server * `socks5h://localhost:9050` : Local SOCKSv5 proxy server with DNS handled by the proxy --netif :: Network interface or local IP address used for outgoing connections. You have to specify IP address bound to some of your local network interfaces, when specifying an IP address. --ip-proto :: Which IP protocol to prefer when connecting to mega.nz (v4, v6, or any). This is just an advisory option. Megatools will resolve mega.nz domain, and then use an A or AAAA record based on the stated preference. megatools-1.11.3.20250203/docs/remote-paths.txt000066400000000000000000000024521474777720000206060ustar00rootroot00000000000000REMOTE FILESYSTEM ----------------- Mega.nz filesystem is represented as a tree of nodes of various types. Nodes are identified by a 8 character node handles (eg. `7Fdi3ZjC`). Structure of the filesystem is not encrypted. Megatools maps node tree structure to a traditional filesystem paths (eg. `/Root/SomeFile.DAT`). *NOTE*: By the nature of Mega.nz storage, several files in the directory can have the same name. To allow access to such files, the names of conflicting files are extended by appending dot and their node handle like this: --------- /Root/conflictingfile /Root/conflictingfile.7Fdi3ZjC /Root/conflictingfile.mEU23aSD --------- You need to be aware of several special folders: /Root:: Writable directory representing the root of the filesystem. /Trash:: Trash directory where Mega.nz web client moves deleted files. This directory is not used by megatools when removing files. /Inbox:: Not sure. /Contacts:: Directory containing subdirectories representing your contacts list. If you want to add contacts to the list, simply create subdirectory named after the contact you want to add. /Contacts/:: Directories representing individual contacts in your contacts list. These directories contain folders that others shared with you. All shared files are read-only, at the moment. megatools-1.11.3.20250203/docs/upload-options.txt000066400000000000000000000004711474777720000211520ustar00rootroot00000000000000--enable-previews:: Generate and upload file previews, when uploading new files. This is the default, unless you changed it in the configuration file. Use this to override the option from the configuration file. --disable-previews:: Don't generate and upload file previews. Default is to generate previews. megatools-1.11.3.20250203/install-symlinks.sh000066400000000000000000000003331474777720000203520ustar00rootroot00000000000000#!/bin/sh bindir="$1" mkdir -p "${DESTDIR}/${MESON_INSTALL_PREFIX}/$bindir" for cmd in df dl get ls test export mkdir put reg rm copy do ln -snf megatools "${DESTDIR}/${MESON_INSTALL_PREFIX}/$bindir/mega$cmd" done megatools-1.11.3.20250203/lib/000077500000000000000000000000001474777720000152505ustar00rootroot00000000000000megatools-1.11.3.20250203/lib/alloc.h000066400000000000000000000053161474777720000165200ustar00rootroot00000000000000#ifndef __ALLOC_H__ #define __ALLOC_H__ #include G_BEGIN_DECLS #define DEFINE_CLEANUP_FUNCTION_FULL(Type, func, null_safe, args...) \ static inline void __cleanup_##func(void *v) \ { \ if (null_safe || *(Type *)v) \ func(*(Type *)v, ##args); \ } #define DEFINE_CLEANUP_FUNCTION(Type, func) DEFINE_CLEANUP_FUNCTION_FULL(Type, func, TRUE) #define DEFINE_CLEANUP_FUNCTION_NULL(Type, func) DEFINE_CLEANUP_FUNCTION_FULL(Type, func, FALSE) #define CLEANUP(func) __attribute__((cleanup(__cleanup_##func))) DEFINE_CLEANUP_FUNCTION(void *, g_free) #define gc_free CLEANUP(g_free) DEFINE_CLEANUP_FUNCTION(char **, g_strfreev) #define gc_strfreev CLEANUP(g_strfreev) DEFINE_CLEANUP_FUNCTION_NULL(GError *, g_error_free) #define gc_error_free CLEANUP(g_error_free) DEFINE_CLEANUP_FUNCTION_NULL(GArray *, g_array_unref) #define gc_array_unref CLEANUP(g_array_unref) DEFINE_CLEANUP_FUNCTION_NULL(GPtrArray *, g_ptr_array_unref) #define gc_ptr_array_unref CLEANUP(g_ptr_array_unref) DEFINE_CLEANUP_FUNCTION_NULL(GByteArray *, g_byte_array_unref) #define gc_byte_array_unref CLEANUP(g_byte_array_unref) DEFINE_CLEANUP_FUNCTION_NULL(GHashTable *, g_hash_table_unref) #define gc_hash_table_unref CLEANUP(g_hash_table_unref) DEFINE_CLEANUP_FUNCTION_NULL(GVariant *, g_variant_unref) #define gc_variant_unref CLEANUP(g_variant_unref) DEFINE_CLEANUP_FUNCTION_NULL(GVariantIter *, g_variant_iter_free) #define gc_variant_iter_free CLEANUP(g_variant_iter_free) DEFINE_CLEANUP_FUNCTION_NULL(GVariantBuilder *, g_variant_builder_unref) #define gc_variant_builder_unref CLEANUP(g_variant_builder_unref) DEFINE_CLEANUP_FUNCTION_NULL(GBytes *, g_bytes_unref) #define gc_bytes_unref CLEANUP(g_bytes_unref) DEFINE_CLEANUP_FUNCTION_NULL(GRegex *, g_regex_unref) #define gc_regex_unref CLEANUP(g_regex_unref) DEFINE_CLEANUP_FUNCTION_NULL(GMatchInfo *, g_match_info_unref) #define gc_match_info_unref CLEANUP(g_match_info_unref) DEFINE_CLEANUP_FUNCTION_NULL(GKeyFile *, g_key_file_unref) #define gc_key_file_unref CLEANUP(g_key_file_unref) DEFINE_CLEANUP_FUNCTION_NULL(GChecksum *, g_checksum_free) #define gc_checksum_free CLEANUP(g_checksum_free) DEFINE_CLEANUP_FUNCTION_NULL(GObject *, g_object_unref) #define gc_object_unref CLEANUP(g_object_unref) DEFINE_CLEANUP_FUNCTION_FULL(GString *, g_string_free, FALSE, TRUE) #define gc_string_free CLEANUP(g_string_free) G_END_DECLS #endif megatools-1.11.3.20250203/lib/http.c000066400000000000000000000344521474777720000164030ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "http.h" #include "mega.h" #include "config.h" #include #include char* http_netif = NULL; int http_ipproto = HTTP_IPPROTO_ANY; #define MEGA_NZ_API_PUBKEY_PIN "sha256//0W38e765pAfPqS3DqSVOrPsC4MEOvRBaXQ7nY1AJ47E=" // curlver.h: this macro was added in May 14, 2015 #ifndef CURL_AT_LEAST_VERSION #define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | z) #define CURL_AT_LEAST_VERSION(x, y, z) (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z)) #endif //#define STEALTH_MODE #if CURL_AT_LEAST_VERSION(7, 12, 0) static CURLSH *http_share; static GRWLock http_locks[CURL_LOCK_DATA_LAST]; static void http_lock_cb(CURL *handle, curl_lock_data data, curl_lock_access access, void *userptr) { if (access == CURL_LOCK_ACCESS_SHARED) g_rw_lock_reader_lock(&http_locks[data]); else g_rw_lock_writer_lock(&http_locks[data]); } static void http_unlock_cb(CURL *handle, curl_lock_data data, void *userptr) { // the implementation is the same for reader/writer unlock (so we just use // writer_unlock) g_rw_lock_writer_unlock(&http_locks[data]); } G_LOCK_DEFINE_STATIC(http_init); void http_init(void) { G_LOCK(http_init); if (!http_share) { http_share = curl_share_init(); //curl_share_setopt(http_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE); curl_share_setopt(http_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS); //curl_share_setopt(http_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION); //curl_share_setopt(http_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); for (int i = 0; i < G_N_ELEMENTS(http_locks); i++) g_rw_lock_init(&http_locks[i]); curl_share_setopt(http_share, CURLSHOPT_LOCKFUNC, http_lock_cb); curl_share_setopt(http_share, CURLSHOPT_UNLOCKFUNC, http_unlock_cb); } G_UNLOCK(http_init); } void http_cleanup(void) { G_LOCK(http_init); if (http_share) { curl_share_cleanup(http_share); http_share = NULL; for (int i = 0; i < G_N_ELEMENTS(http_locks); i++) g_rw_lock_clear(&http_locks[i]); } G_UNLOCK(http_init); } #else void http_init(void) {} void http_cleanup(void) {} #endif struct http { CURL *curl; GHashTable *headers; http_progress_fn progress_cb; gpointer progress_data; }; struct http *http_new(void) { struct http *h = g_new0(struct http, 1); h->curl = curl_easy_init(); if (!h->curl) { g_free(h); return NULL; } #if CURL_AT_LEAST_VERSION(7, 57, 0) http_init(); curl_easy_setopt(h->curl, CURLOPT_SHARE, http_share); #endif #if CURL_AT_LEAST_VERSION(7, 21, 6) curl_easy_setopt(h->curl, CURLOPT_ACCEPT_ENCODING, ""); #else curl_easy_setopt(h->curl, CURLOPT_ENCODING, ""); #endif switch (http_ipproto) { case HTTP_IPPROTO_V4: curl_easy_setopt(h->curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); break; case HTTP_IPPROTO_V6: curl_easy_setopt(h->curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); break; } if (http_netif) curl_easy_setopt(h->curl, CURLOPT_INTERFACE, http_netif); if (mega_debug & MEGA_DEBUG_HTTP) curl_easy_setopt(h->curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(h->curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(h->curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(h->curl, CURLOPT_PROTOCOLS_STR, "http,https"); curl_easy_setopt(h->curl, CURLOPT_TCP_KEEPALIVE, 1L); curl_easy_setopt(h->curl, CURLOPT_TCP_KEEPIDLE, 120L); curl_easy_setopt(h->curl, CURLOPT_TCP_KEEPINTVL, 60L); curl_easy_setopt(h->curl, CURLOPT_BUFFERSIZE, 256 * 1024L); curl_easy_setopt(h->curl, CURLOPT_SSL_SESSIONID_CACHE, 0L); #if CURL_AT_LEAST_VERSION(7, 44, 0) const gchar* pkp_disable = g_getenv("MEGATOOLS_PKP_DISABLE"); if (pkp_disable == NULL || strcmp(pkp_disable, "1")) { if (curl_easy_setopt(h->curl, CURLOPT_PINNEDPUBLICKEY, MEGA_NZ_API_PUBKEY_PIN) != CURLE_OK) { g_printerr("ERROR: Failed to setup public key pinning, aborting! You probably need a newer cURL library.\n"); exit(1); } else { curl_easy_setopt(h->curl, CURLOPT_CAINFO, NULL); curl_easy_setopt(h->curl, CURLOPT_SSL_VERIFYPEER, 0L); } } #endif h->headers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); #if STEALTH_MODE // we are Firefox! http_set_header(h, "User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0"); http_set_header(h, "Referer", "https://mega.nz/"); http_set_header(h, "Origin", "https://mega.nz"); http_set_header(h, "Accept", "*/*"); http_set_header(h, "Accept-Language", "en-US;q=0.8,en;q=0.3"); http_set_header(h, "Cache-Control", "no-cache"); http_set_header(h, "Pragma", "no-cache"); http_set_header(h, "DNT", "1"); #else // set default headers http_set_header(h, "User-Agent", "Megatools (" VERSION ")"); #endif // Disable 100-continue (because it causes needless roundtrips) http_set_header(h, "Expect", ""); return h; } void http_set_max_connects(struct http *h, long max) { g_return_if_fail(h != NULL); curl_easy_setopt(h->curl, CURLOPT_MAXCONNECTS, max); } void http_expect_short_running(struct http *h) { g_return_if_fail(h != NULL); // don't use alarm signal to time out dns queries curl_easy_setopt(h->curl, CURLOPT_TIMEOUT, 10*60L); // 10 minutes max per connection curl_easy_setopt(h->curl, CURLOPT_LOW_SPEED_TIME, 60L); // 60s max of very low speed curl_easy_setopt(h->curl, CURLOPT_LOW_SPEED_LIMIT, 10L); } void http_set_header(struct http *h, const gchar *name, const gchar *value) { g_return_if_fail(h != NULL); g_return_if_fail(name != NULL); g_return_if_fail(value != NULL); g_hash_table_insert(h->headers, g_strdup(name), g_strdup(value)); } void http_set_content_type(struct http *h, const gchar *type) { http_set_header(h, "Content-Type", type); } void http_set_content_length(struct http *h, goffset len) { gchar *tmp = g_strdup_printf("%" G_GOFFSET_FORMAT, len); http_set_header(h, "Content-Length", tmp); g_free(tmp); } static int curl_progress(struct http *h, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { if (h->progress_cb) { if (!h->progress_cb(dltotal, dlnow, ultotal, ulnow, h->progress_data)) return 1; // cancel } return 0; } void http_set_speed(struct http *h, gint max_ul, gint max_dl) { if (max_ul >= 0) curl_easy_setopt(h->curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t)max_ul * 1024); if (max_dl >= 0) curl_easy_setopt(h->curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)max_dl * 1024); } void http_set_proxy(struct http *h, const gchar *proxy) { curl_easy_setopt(h->curl, CURLOPT_PROXY, proxy); } void http_set_progress_callback(struct http *h, http_progress_fn cb, gpointer data) { if (cb) { h->progress_cb = cb; h->progress_data = data; curl_easy_setopt(h->curl, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(h->curl, CURLOPT_XFERINFOFUNCTION, curl_progress); curl_easy_setopt(h->curl, CURLOPT_PROGRESSDATA, h); } else { curl_easy_setopt(h->curl, CURLOPT_NOPROGRESS, 1L); } } static void add_header(gchar *key, gchar *val, struct curl_slist **l) { gchar *tmp = g_strdup_printf("%s: %s", key, val); *l = curl_slist_append(*l, tmp); g_free(tmp); } static size_t append_gstring(void *buffer, size_t size, size_t nmemb, GString *str) { if (size * nmemb > 0) g_string_append_len(str, buffer, size * nmemb); return nmemb; } static gboolean to_error(struct http* h, CURLcode res, GError** err) { glong http_status = 0; if (res == CURLE_OK) { if (curl_easy_getinfo(h->curl, CURLINFO_RESPONSE_CODE, &http_status) == CURLE_OK) { if (http_status == 200 || http_status == 201) return FALSE; else if (http_status == 500) g_set_error(err, HTTP_ERROR, HTTP_ERROR_SERVER_BUSY, "Server returned %ld (probably busy)", http_status); else if (http_status == 509) g_set_error(err, HTTP_ERROR, HTTP_ERROR_BANDWIDTH_LIMIT, "Server returned %ld (over quota)", http_status); else g_set_error(err, HTTP_ERROR, HTTP_ERROR_OTHER, "Server returned %ld", http_status); } else g_set_error(err, HTTP_ERROR, HTTP_ERROR_OTHER, "Can't get http status code"); } else if (res == CURLE_OPERATION_TIMEDOUT) g_set_error(err, HTTP_ERROR, HTTP_ERROR_TIMEOUT, "CURL timeout: %s", curl_easy_strerror(res)); else if (res == CURLE_RECV_ERROR || res == CURLE_SEND_ERROR || res == CURLE_SSL_CONNECT_ERROR || res == CURLE_UPLOAD_FAILED #if CURL_AT_LEAST_VERSION(7, 50, 3) || res == CURLE_WEIRD_SERVER_REPLY #endif #if CURL_AT_LEAST_VERSION(7, 49, 0) || res == CURLE_HTTP2_STREAM #endif #if CURL_AT_LEAST_VERSION(7, 38, 0) || res == CURLE_HTTP2 #endif || res == CURLE_COULDNT_CONNECT) g_set_error(err, HTTP_ERROR, HTTP_ERROR_COMM_FAILURE, "CURL error: %s", curl_easy_strerror(res)); else if (res == CURLE_GOT_NOTHING) g_set_error(err, HTTP_ERROR, HTTP_ERROR_NO_RESPONSE, "CURL error: %s", curl_easy_strerror(res)); else g_set_error(err, HTTP_ERROR, HTTP_ERROR_OTHER, "CURL error: %s", curl_easy_strerror(res)); return TRUE; } GString *http_post(struct http *h, const gchar *url, const gchar *body, gssize body_len, GError **err) { struct curl_slist *headers = NULL; GString *response; CURLcode res; g_return_val_if_fail(h != NULL, NULL); g_return_val_if_fail(url != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); // setup post headers and url curl_easy_setopt(h->curl, CURLOPT_POST, 1L); curl_easy_setopt(h->curl, CURLOPT_URL, url); g_hash_table_foreach(h->headers, (GHFunc)add_header, &headers); curl_easy_setopt(h->curl, CURLOPT_HTTPHEADER, headers); // pass request body if (body) { curl_easy_setopt(h->curl, CURLOPT_NOBODY, 0L); curl_easy_setopt(h->curl, CURLOPT_POSTFIELDS, body); curl_easy_setopt(h->curl, CURLOPT_POSTFIELDSIZE, (long)body_len); } else { curl_easy_setopt(h->curl, CURLOPT_NOBODY, 1L); curl_easy_setopt(h->curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(h->curl, CURLOPT_POSTFIELDSIZE, 0L); } // prepare buffer for the response body response = g_string_sized_new(1024); curl_easy_setopt(h->curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)append_gstring); curl_easy_setopt(h->curl, CURLOPT_WRITEDATA, response); // perform HTTP request res = curl_easy_perform(h->curl); if (to_error(h, res, err)) { g_string_free(response, TRUE); response = NULL; } curl_easy_setopt(h->curl, CURLOPT_HTTPHEADER, NULL); curl_slist_free_all(headers); return response; } struct stream_data { http_data_fn cb; gpointer user_data; }; static size_t curl_read(void *buffer, size_t size, size_t nmemb, struct stream_data *data) { return data->cb(buffer, size * nmemb, data->user_data); } GString *http_post_stream_upload(struct http *h, const gchar *url, goffset len, http_data_fn read_cb, gpointer user_data, GError **err) { struct curl_slist *headers = NULL; GString *response; CURLcode res; struct stream_data data; g_return_val_if_fail(h != NULL, NULL); g_return_val_if_fail(url != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); // setup post headers and url curl_easy_setopt(h->curl, CURLOPT_POST, 1L); curl_easy_setopt(h->curl, CURLOPT_URL, url); // setup request post body writer http_set_content_length(h, len); curl_easy_setopt(h->curl, CURLOPT_POSTFIELDSIZE_LARGE, len); data.cb = read_cb; data.user_data = user_data; curl_easy_setopt(h->curl, CURLOPT_READFUNCTION, (curl_read_callback)curl_read); curl_easy_setopt(h->curl, CURLOPT_READDATA, &data); // prepare buffer for the response body response = g_string_sized_new(512); curl_easy_setopt(h->curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)append_gstring); curl_easy_setopt(h->curl, CURLOPT_WRITEDATA, response); g_hash_table_foreach(h->headers, (GHFunc)add_header, &headers); curl_easy_setopt(h->curl, CURLOPT_HTTPHEADER, headers); // perform HTTP request res = curl_easy_perform(h->curl); if (to_error(h, res, err)) { g_string_free(response, TRUE); response = NULL; } curl_easy_setopt(h->curl, CURLOPT_HTTPHEADER, NULL); curl_slist_free_all(headers); return response; } static size_t curl_write(void *buffer, size_t size, size_t nmemb, struct stream_data *data) { return data->cb(buffer, size * nmemb, data->user_data); } static CURLcode curl_easy_perform_retry_empty(CURL *curl) { gint delay = 250000; // repeat after 250ms 500ms 1s ... CURLcode res; again: res = curl_easy_perform(curl); if (res == CURLE_GOT_NOTHING) { g_usleep(delay); delay = delay * 2; if (delay > 4 * 1000 * 1000) return CURLE_GOT_NOTHING; goto again; } return res; } gboolean http_post_stream_download(struct http *h, const gchar *url, http_data_fn write_cb, gpointer user_data, GError **err) { struct curl_slist *headers = NULL; CURLcode res; struct stream_data data; gboolean status = TRUE; g_return_val_if_fail(h != NULL, FALSE); g_return_val_if_fail(url != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); // setup post headers and url curl_easy_setopt(h->curl, CURLOPT_POST, 1L); curl_easy_setopt(h->curl, CURLOPT_URL, url); // request is empty curl_easy_setopt(h->curl, CURLOPT_POSTFIELDSIZE, 0); // setup response writer data.cb = write_cb; data.user_data = user_data; curl_easy_setopt(h->curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)curl_write); curl_easy_setopt(h->curl, CURLOPT_WRITEDATA, &data); g_hash_table_foreach(h->headers, (GHFunc)add_header, &headers); curl_easy_setopt(h->curl, CURLOPT_HTTPHEADER, headers); // perform HTTP request res = curl_easy_perform_retry_empty(h->curl); if (to_error(h, res, err)) status = FALSE; curl_easy_setopt(h->curl, CURLOPT_HTTPHEADER, NULL); curl_slist_free_all(headers); return status; } void http_free(struct http *h) { if (!h) return; g_hash_table_unref(h->headers); curl_easy_cleanup(h->curl); memset(h, 0, sizeof(struct http)); g_free(h); } GQuark http_error_quark(void) { return g_quark_from_static_string("http-error-quark"); } megatools-1.11.3.20250203/lib/http.h000066400000000000000000000046421474777720000164060ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __MEGA_HTTP_H #define __MEGA_HTTP_H #include #define HTTP_ERROR http_error_quark() enum { HTTP_ERROR_NO_RESPONSE, HTTP_ERROR_TIMEOUT, HTTP_ERROR_SERVER_BUSY, HTTP_ERROR_COMM_FAILURE, HTTP_ERROR_BANDWIDTH_LIMIT, HTTP_ERROR_OTHER }; enum { HTTP_IPPROTO_ANY, HTTP_IPPROTO_V4, HTTP_IPPROTO_V6, }; typedef gsize (*http_data_fn)(gpointer buf, gsize len, gpointer user_data); typedef gboolean (*http_progress_fn)(goffset dltotal, goffset dlnow, goffset ultotal, goffset ulnow, gpointer user_data); // globals extern char* http_netif; extern int http_ipproto; // functions struct http *http_new(void); void http_set_max_connects(struct http *h, long max); void http_expect_short_running(struct http *h); void http_set_content_type(struct http *h, const gchar *type); void http_set_content_length(struct http *h, goffset len); void http_set_header(struct http *h, const gchar *name, const gchar *value); void http_set_progress_callback(struct http *h, http_progress_fn cb, gpointer data); void http_set_speed(struct http *h, gint max_ul, gint max_dl); void http_set_proxy(struct http *h, const gchar *proxy); GString *http_post(struct http *h, const gchar *url, const gchar *body, gssize body_len, GError **err); GString *http_post_stream_upload(struct http *h, const gchar *url, goffset len, http_data_fn read_cb, gpointer user_data, GError **err); gboolean http_post_stream_download(struct http *h, const gchar *url, http_data_fn write_cb, gpointer user_data, GError **err); void http_free(struct http *h); void http_cleanup(void); GQuark http_error_quark(void); #endif megatools-1.11.3.20250203/lib/mega.c000066400000000000000000004374541474777720000163460ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include "mega.h" #include "http.h" #include "sjson.h" #include "alloc.h" #include #include #include #include #include #include #include #include #include #include #include #include DEFINE_CLEANUP_FUNCTION(struct http *, http_free) #define gc_http_free CLEANUP(http_free) DEFINE_CLEANUP_FUNCTION_NULL(BN_CTX *, BN_CTX_free) #define gc_bn_ctx_free CLEANUP(BN_CTX_free) DEFINE_CLEANUP_FUNCTION_NULL(BIGNUM *, BN_free) #define gc_bn_free CLEANUP(BN_free) #define CACHE_FORMAT_VERSION 4 gint mega_debug = 0; // Data structures and enums // {{{ SRV_E* enum { SRV_EINTERNAL = -1, SRV_EARGS = -2, SRV_EAGAIN = -3, SRV_ERATELIMIT = -4, SRV_EFAILED = -5, SRV_ETOOMANY = -6, SRV_ERANGE = -7, SRV_EEXPIRED = -8, // FS access errors SRV_ENOENT = -9, SRV_ECIRCULAR = -10, SRV_EACCESS = -11, SRV_EEXIST = -12, SRV_EINCOMPLETE = -13, // crypto errors SRV_EKEY = -14, // user errors SRV_ESID = -15, SRV_EBLOCKED = -16, SRV_EOVERQUOTA = -17, SRV_ETEMPUNAVAIL = -18, SRV_ETOOMANYCONNECTIONS = -19 }; // }}} // {{{ rsa_key struct rsa_key { // priv BIGNUM *p; BIGNUM *q; BIGNUM *d; BIGNUM *u; // p^-1 mod q // pub BIGNUM *m; BIGNUM *e; }; // }}} // {{{ struct mega_session struct mega_session { struct http *http; gint max_ul; gint max_dl; gchar *proxy; gint max_workers; gint id; gchar *sid; gchar *rid; GHashTable *api_url_params; gchar *password_salt_v2; /* as returned from mega.nz (base64 string) */ guchar *password_key; guchar *password_key_save; /* password key for session saver */ guchar *master_key; struct rsa_key rsa_key; gchar *user_handle; gchar *user_name; gchar *user_email; GHashTable *share_keys; GSList *fs_nodes; // progress reporting mega_status_callback status_callback; gpointer status_userdata; gint64 last_refresh; gboolean create_preview; gboolean resume_enabled; }; // }}} // JSON helpers // {{{ print_node static void print_node(const gchar *n, const gchar *prefix) { gc_free gchar *pretty = s_json_pretty(n); g_printerr("%s%s\n", prefix, pretty); } // }}} // {{{ s_json_get_bytes static guchar *s_json_get_bytes(const gchar *node, gsize *out_len) { g_return_val_if_fail(node != NULL, NULL); g_return_val_if_fail(out_len != NULL, NULL); gc_free gchar *data = s_json_get_string(node); if (data) { gchar *b64 = g_base64_decode(data, out_len); return b64; } return NULL; } // }}} // {{{ s_json_get_member_bytes static guchar *s_json_get_member_bytes(const gchar *node, const gchar *name, gsize *out_len) { g_return_val_if_fail(node != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); g_return_val_if_fail(out_len != NULL, NULL); const gchar *v = s_json_get_member(node, name); if (v) return s_json_get_bytes(v, out_len); return NULL; } // }}} // {{{ s_json_get_member_bn static BIGNUM *s_json_get_member_bn(const gchar *node, const gchar *name) { g_return_val_if_fail(node != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); gc_free gchar *data = s_json_get_member_string(node, name); if (data) { BIGNUM *n = NULL; if (BN_dec2bn(&n, data)) return n; } return NULL; } void s_json_get_member_rsa_key(const gchar *node, const gchar *name, struct rsa_key *key) { g_return_if_fail(node != NULL); g_return_if_fail(name != NULL); g_return_if_fail(key != NULL); const gchar *member = s_json_get_member(node, name); if (!member || s_json_get_type(member) != S_JSON_TYPE_OBJECT) return; #define READ_COMPONENT(c) key->c = s_json_get_member_bn(member, #c) READ_COMPONENT(p); READ_COMPONENT(q); READ_COMPONENT(d); READ_COMPONENT(u); READ_COMPONENT(m); READ_COMPONENT(e); #undef READ_COMPONENT } // }}} // {{{ s_json_gen_member_bytes static void s_json_gen_member_bytes(SJsonGen *gen, const gchar *name, guchar *data, gsize len) { if (data) { gc_free gchar *tmp = g_base64_encode(data, len); s_json_gen_member_string(gen, name, tmp); } else { s_json_gen_member_null(gen, name); } } // }}} // {{{ s_json_gen_member_rsa_key static void s_json_gen_member_bn(SJsonGen *gen, const gchar *name, BIGNUM *n) { gchar *tmp = BN_bn2dec(n); s_json_gen_member_string(gen, name, tmp); OPENSSL_free(tmp); } static void s_json_gen_member_rsa_key(SJsonGen *gen, const gchar *name, struct rsa_key *key) { s_json_gen_member_object(gen, name); #define ADD_COMPONENT(name) \ if (key->name) \ s_json_gen_member_bn(gen, #name, key->name); ADD_COMPONENT(p) ADD_COMPONENT(q) ADD_COMPONENT(d) ADD_COMPONENT(u) ADD_COMPONENT(m) ADD_COMPONENT(e) #undef ADD_COMPONENT s_json_gen_end_object(gen); } // }}} // Crypto utilities #define DW(p, n) (*((guint32 *)(p) + (n))) // {{{ multi-precision integer macros #define MPI_BITS(ptr) GUINT16_FROM_BE(*(guint16 *)(ptr)) #define MPI_BYTES(ptr) ((MPI_BITS(ptr) + 7) / 8) #define MPI_SIZE(ptr) (MPI_BYTES(ptr) + MPI_HDRSIZE) #define MPI_HDRSIZE 2 #define MPI2BN(ptr) BN_bin2bn((ptr) + MPI_HDRSIZE, MPI_BYTES(ptr), NULL) // }}} // {{{ base64urlencode static gchar *base64urlencode(const guchar *data, gsize len) { gint i, shl; gchar *she, *p; g_return_val_if_fail(data != NULL, NULL); g_return_val_if_fail(len > 0, NULL); gc_free gchar *sh = g_base64_encode(data, len); shl = strlen(sh); she = g_malloc0(shl + 1), p = she; for (i = 0; i < shl; i++) { if (sh[i] == '+') *p = '-'; else if (sh[i] == '/') *p = '_'; else if (sh[i] == '=') continue; else *p = sh[i]; p++; } *p = '\0'; return she; } // }}} // {{{ base64urldecode static guchar *base64urldecode(const gchar *str, gsize *len) { gint i; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(len != NULL, NULL); gc_string_free GString *s = g_string_new(str); for (i = 0; i < s->len; i++) { if (s->str[i] == '-') s->str[i] = '+'; else if (s->str[i] == '_') s->str[i] = '/'; } gint eqs = (s->len * 3) & 0x03; for (i = 0; i < eqs; i++) g_string_append_c(s, '='); return g_base64_decode(s->str, len); } // }}} // {{{ aes128_decrypt G_GNUC_UNUSED static gboolean aes128_decrypt(guchar *out, const guchar *in, gsize len, const guchar *key) { AES_KEY k; gsize off; g_return_val_if_fail(out != NULL, FALSE); g_return_val_if_fail(in != NULL, FALSE); g_return_val_if_fail(len % 16 == 0, FALSE); g_return_val_if_fail(key != NULL, FALSE); AES_set_decrypt_key(key, 128, &k); for (off = 0; off < len; off += 16) AES_decrypt(in + off, out + off, &k); return TRUE; } // }}} // {{{ aes128_encrypt static gboolean aes128_encrypt(guchar *out, const guchar *in, gsize len, const guchar *key) { AES_KEY k; gsize off; g_return_val_if_fail(out != NULL, FALSE); g_return_val_if_fail(in != NULL, FALSE); g_return_val_if_fail(len % 16 == 0, FALSE); g_return_val_if_fail(key != NULL, FALSE); AES_set_encrypt_key(key, 128, &k); for (off = 0; off < len; off += 16) AES_encrypt(in + off, out + off, &k); return TRUE; } // }}} // {{{ b64_aes128_decrypt static guchar *b64_aes128_decrypt(const gchar *str, const guchar *key, gsize *outlen) { AES_KEY k; gsize cipherlen = 0; gsize off; gc_free guchar *cipher = NULL; guchar *data; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); AES_set_decrypt_key(key, 128, &k); cipher = base64urldecode(str, &cipherlen); if (cipher == NULL) return NULL; if (cipherlen % 16 != 0) return NULL; data = g_malloc0(cipherlen); for (off = 0; off < cipherlen; off += 16) AES_decrypt(cipher + off, data + off, &k); if (outlen) *outlen = cipherlen; return data; } // }}} // {{{ b64_aes128_encrypt static gchar *b64_aes128_encrypt(const guchar *data, gsize len, const guchar *key) { AES_KEY k; gsize off; gc_free guchar *cipher = NULL; gchar *str; g_return_val_if_fail(data != NULL, NULL); g_return_val_if_fail((len % 16) == 0, NULL); g_return_val_if_fail(key != NULL, NULL); AES_set_encrypt_key(key, 128, &k); cipher = g_malloc0(len); for (off = 0; off < len; off += 16) AES_encrypt(data + off, cipher + off, &k); return base64urlencode(cipher, len); } // }}} // {{{ b64_aes128_cbc_decrypt static guchar *b64_aes128_cbc_decrypt(const gchar *str, const guchar *key, gsize *outlen) { AES_KEY k; gsize cipherlen = 0; gc_free guchar *cipher = NULL; guchar *data; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); AES_set_decrypt_key(key, 128, &k); cipher = base64urldecode(str, &cipherlen); if (cipher == NULL) return NULL; if (cipherlen % 16 != 0) return NULL; data = g_malloc0(cipherlen + 1); guchar iv[AES_BLOCK_SIZE] = { 0 }; AES_cbc_encrypt(cipher, data, cipherlen, &k, iv, 0); if (outlen) *outlen = cipherlen; return data; } // }}} // {{{ b64_aes128_cbc_encrypt static gchar *b64_aes128_cbc_encrypt(const guchar *data, gsize len, const guchar *key) { AES_KEY k; gc_free guchar *cipher = NULL; gchar *str; guchar iv[AES_BLOCK_SIZE] = { 0 }; g_return_val_if_fail(data != NULL, NULL); g_return_val_if_fail((len % 16) == 0, NULL); g_return_val_if_fail(key != NULL, NULL); AES_set_encrypt_key(key, 128, &k); cipher = g_malloc0(len); AES_cbc_encrypt(data, cipher, len, &k, iv, 1); return base64urlencode(cipher, len); } // }}} // {{{ b64_aes128_cbc_encrypt_str static gchar *b64_aes128_cbc_encrypt_str(const gchar *str, const guchar *key) { gsize str_len, aligned_len; gc_free guchar *data = NULL; gchar *out; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); str_len = strlen(str); aligned_len = str_len + 1; if (aligned_len % 16) aligned_len += 16 - (aligned_len % 16); data = g_malloc0(aligned_len); memcpy(data, str, str_len); return b64_aes128_cbc_encrypt(data, aligned_len, key); } // }}} // {{{ b64_aes128_decrypt_privk static gboolean b64_aes128_decrypt_privk(const gchar *str, const guchar *key, struct rsa_key *rsa) { gsize data_len = 0; gc_free guchar *data = NULL; guchar *p, *e; g_return_val_if_fail(str != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(rsa != NULL, FALSE); data = b64_aes128_decrypt(str, key, &data_len); if (!data) return FALSE; p = data; e = p + data_len; if (p + MPI_SIZE(p) > e) return FALSE; rsa->p = MPI2BN(p); p += MPI_SIZE(p); if (p + MPI_SIZE(p) > e) return FALSE; rsa->q = MPI2BN(p); p += MPI_SIZE(p); if (p + MPI_SIZE(p) > e) return FALSE; rsa->d = MPI2BN(p); p += MPI_SIZE(p); if (p + MPI_SIZE(p) > e) return FALSE; rsa->u = MPI2BN(p); return TRUE; } // }}} // {{{ b64_decode_pubk static gboolean b64_decode_pubk(const gchar *str, struct rsa_key *rsa) { gsize data_len = 0; gc_free guchar *data = NULL; guchar *p, *e; g_return_val_if_fail(str != NULL, FALSE); g_return_val_if_fail(rsa != NULL, FALSE); data = base64urldecode(str, &data_len); if (data == NULL) return FALSE; p = data; e = p + data_len; if (p + MPI_SIZE(p) > e) return FALSE; rsa->m = MPI2BN(p); p += MPI_SIZE(p); if (p + MPI_SIZE(p) > e) return FALSE; rsa->e = MPI2BN(p); return TRUE; } // }}} // {{{ b64_aes128_encrypt_privk static void append_mpi_from_bn(GString *buf, BIGNUM *n) { g_return_if_fail(buf != NULL); g_return_if_fail(n != NULL); gsize size = BN_num_bytes(n); gsize off = buf->len; g_string_set_size(buf, buf->len + size + MPI_HDRSIZE); *(guint16 *)(buf->str + off) = GUINT16_TO_BE(BN_num_bits(n)); BN_bn2bin(n, buf->str + off + MPI_HDRSIZE); } static gchar *b64_aes128_encrypt_privk(const guchar *key, struct rsa_key *rsa) { g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(rsa != NULL, FALSE); gc_string_free GString *data = g_string_sized_new(128 * 7); append_mpi_from_bn(data, rsa->p); append_mpi_from_bn(data, rsa->q); append_mpi_from_bn(data, rsa->d); append_mpi_from_bn(data, rsa->u); gsize off = data->len; gsize pad = data->len % 16 ? 16 - (data->len % 16) : 0; if (pad) { g_string_set_size(data, data->len + pad); while (off < data->len) data->str[off++] = 0; } return b64_aes128_encrypt(data->str, data->len, key); } // }}} // {{{ b64_encode_pubk static gchar *b64_encode_pubk(struct rsa_key *rsa) { g_return_val_if_fail(rsa != NULL, FALSE); gc_string_free GString *data = g_string_sized_new(128 * 3); append_mpi_from_bn(data, rsa->m); append_mpi_from_bn(data, rsa->e); return base64urlencode(data->str, data->len); } // }}} // {{{ rsa_decrypt static BIGNUM *rsa_decrypt(BIGNUM *m, BIGNUM *d, BIGNUM *p, BIGNUM *q, BIGNUM *u) { g_return_val_if_fail(m != NULL, NULL); g_return_val_if_fail(d != NULL, NULL); g_return_val_if_fail(p != NULL, NULL); g_return_val_if_fail(q != NULL, NULL); g_return_val_if_fail(u != NULL, NULL); gc_bn_ctx_free BN_CTX *ctx = BN_CTX_new(); gc_bn_free BIGNUM *xp = BN_new(); gc_bn_free BIGNUM *xq = BN_new(); gc_bn_free BIGNUM *mod_mp = BN_new(); gc_bn_free BIGNUM *mod_mq = BN_new(); gc_bn_free BIGNUM *mod_dp1 = BN_new(); gc_bn_free BIGNUM *mod_dq1 = BN_new(); gc_bn_free BIGNUM *p1 = BN_new(); gc_bn_free BIGNUM *q1 = BN_new(); gc_bn_free BIGNUM *t = BN_new(); BIGNUM *x = BN_new(); // var xp = bmodexp(bmod(m,p), bmod(d,bsub(p,[1])), p); BN_mod(mod_mp, m, p, ctx); BN_sub(p1, p, BN_value_one()); BN_mod(mod_dp1, d, p1, ctx); BN_mod_exp(xp, mod_mp, mod_dp1, p, ctx); // var xq = bmodexp(bmod(m,q), bmod(d,bsub(q,[1])), q); BN_mod(mod_mq, m, q, ctx); BN_sub(q1, q, BN_value_one()); BN_mod(mod_dq1, d, q1, ctx); BN_mod_exp(xq, mod_mq, mod_dq1, q, ctx); // var t = bsub(xq,xp); if (BN_ucmp(xq, xp) <= 0) { BN_sub(t, xp, xq); BN_mul(x, t, u, ctx); BN_mod(t, x, q, ctx); BN_sub(t, q, t); } else { BN_sub(t, xq, xp); BN_mul(x, t, u, ctx); BN_mod(t, x, q, ctx); } BN_mul(x, t, p, ctx); BN_add(x, x, xp); return x; } // }}} // {{{ rsa_encrypt #if 0 static BIGNUM* rsa_encrypt(BIGNUM* s, BIGNUM* e, BIGNUM* m) { BN_CTX* ctx; BIGNUM *r; g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(e != NULL, NULL); g_return_val_if_fail(m != NULL, NULL); ctx = BN_CTX_new(); r = BN_new(); BN_mod_exp(r, s, e, m, ctx); BN_CTX_free(ctx); return r; } #endif // }}} // {{{ b64_rsa_decrypt static guchar *b64_rsa_decrypt(const gchar *str, struct rsa_key *key, gsize *outlen) { gsize cipherlen = 0; gc_free guchar *cipher = NULL; guchar *data; gc_bn_free BIGNUM *c = NULL, *m = NULL; g_return_val_if_fail(str != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); cipher = base64urldecode(str, &cipherlen); if (cipher == NULL) return NULL; if (MPI_SIZE(cipher) > cipherlen) return NULL; c = MPI2BN(cipher); m = rsa_decrypt(c, key->d, key->p, key->q, key->u); if (!m) return NULL; data = g_malloc0(BN_num_bytes(m) + 1); BN_bn2bin(m, data); if (outlen) *outlen = BN_num_bytes(m); return data; } // }}} // {{{ rsa_key_gen static gboolean rsa_key_gen(struct rsa_key *k) { RSA *key; g_return_val_if_fail(k != NULL, FALSE); g_return_val_if_fail(k->p == NULL, FALSE); g_return_val_if_fail(k->m == NULL, FALSE); key = RSA_new(); if (!key) return FALSE; BIGNUM *e = BN_new(); if (!e) { RSA_free(key); return FALSE; } BN_set_word(e, RSA_3); if (!RSA_generate_key_ex(key, 2048, e, NULL)) return FALSE; if (RSA_check_key(key) != 1) { RSA_free(key); return FALSE; } #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) const BIGNUM *p, *q, *d, *u, *m, *_e; RSA_get0_key(key, &m, &_e, &d); RSA_get0_factors(key, &q, &p); RSA_get0_crt_params(key, NULL, NULL, &u); k->p = BN_dup(p); k->q = BN_dup(q); k->d = BN_dup(d); k->u = BN_dup(u); k->m = BN_dup(m); k->e = BN_dup(_e); #else // private part k->p = BN_dup(key->q); k->q = BN_dup(key->p); k->d = BN_dup(key->d); k->u = BN_dup(key->iqmp); // public part k->m = BN_dup(key->n); k->e = BN_dup(key->e); #endif RSA_free(key); BN_free(e); return TRUE; } // }}} // {{{ rsa_key_free static void rsa_key_free(struct rsa_key *k) { if (!k) return; if (k->p) BN_free(k->p); if (k->q) BN_free(k->q); if (k->d) BN_free(k->d); if (k->u) BN_free(k->u); if (k->m) BN_free(k->m); if (k->e) BN_free(k->e); memset(k, 0, sizeof(struct rsa_key)); } // }}} // {{{ make_random_key static guchar *make_random_key(void) { guchar k[16]; //XXX: error check RAND_bytes(k, sizeof(k)); return g_memdup2(k, 16); } // }}} // {{{ make_password_key static guchar *make_password_key(const gchar *password) { guchar pkey[16] = { 0x93, 0xC4, 0x67, 0xE3, 0x7D, 0xB0, 0xC7, 0xA4, 0xD1, 0xBE, 0x3F, 0x81, 0x01, 0x52, 0xCB, 0x56 }; gint i, r; gint len; g_return_val_if_fail(password != NULL, NULL); len = strlen(password); for (r = 65536; r--;) { for (i = 0; i < len; i += 16) { AES_KEY k; guchar key[16] = { 0 }, pkey_tmp[16]; memcpy(key, password + i, MIN(16, len - i)); AES_set_encrypt_key(key, 128, &k); AES_encrypt(pkey, pkey_tmp, &k); memcpy(pkey, pkey_tmp, 16); } } return g_memdup2(pkey, 16); } // }}} // {{{ make_username_hash static gchar *make_username_hash(const gchar *un, const guchar *key) { AES_KEY k; gint l, i; guchar hash[16] = { 0 }, hash_tmp[16], oh[8]; g_return_val_if_fail(un != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); AES_set_encrypt_key(key, 128, &k); for (i = 0, l = strlen(un); i < l; i++) hash[i % 16] ^= un[i]; for (i = 16384; i--;) { AES_encrypt(hash, hash_tmp, &k); memcpy(hash, hash_tmp, 16); } memcpy(oh, hash, 4); memcpy(oh + 4, hash + 8, 4); return base64urlencode(oh, 8); } // }}} // {{{ make_request_id static guchar *make_request_id(void) { const gchar chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; gchar k[11] = { 0 }; gint i; for (i = 0; i < 10; i++) k[i] = chars[rand() % sizeof(chars)]; return g_strdup(k); } // }}} // {{{ chunk locations static guint get_chunk_size(gsize idx) { return (idx < 8 ? idx + 1 : 8) * 1024 * 128; } // }}} // {{{ chunked CBC-MAC struct chunked_cbc_mac { EVP_CIPHER_CTX *ctx; gsize chunk_idx; guint64 next_boundary; guint64 position; guchar chunk_mac_iv[16]; guchar chunk_mac[16]; guchar meta_mac[16]; }; static gboolean chunked_cbc_mac_init(struct chunked_cbc_mac *mac, guchar key[16], guchar iv[16]) { g_return_val_if_fail(mac != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); memset(mac, 0, sizeof(*mac)); memcpy(mac->chunk_mac_iv, iv, 16); memcpy(mac->chunk_mac, mac->chunk_mac_iv, 16); mac->next_boundary = get_chunk_size(mac->chunk_idx); mac->ctx = EVP_CIPHER_CTX_new(); if (!mac->ctx) return FALSE; if (!EVP_EncryptInit_ex(mac->ctx, EVP_aes_128_ecb(), NULL, key, NULL)) { EVP_CIPHER_CTX_free(mac->ctx); return FALSE; } EVP_CIPHER_CTX_set_padding(mac->ctx, 0); return TRUE; } static gboolean chunked_cbc_mac_init8(struct chunked_cbc_mac *mac, guchar key[16], guchar iv[8]) { g_return_val_if_fail(iv != NULL, FALSE); guchar mac_iv[16]; memcpy(mac_iv, iv, 8); memcpy(mac_iv + 8, iv, 8); return chunked_cbc_mac_init(mac, key, mac_iv); } static gboolean chunked_cbc_mac_close_chunk(struct chunked_cbc_mac *mac) { gint i; int out_len; for (i = 0; i < 16; i++) mac->meta_mac[i] ^= mac->chunk_mac[i]; if (!EVP_EncryptUpdate(mac->ctx, mac->meta_mac, &out_len, mac->meta_mac, 16)) return FALSE; memcpy(mac->chunk_mac, mac->chunk_mac_iv, 16); mac->next_boundary += get_chunk_size(++mac->chunk_idx); return TRUE; } static gboolean chunked_cbc_mac_update(struct chunked_cbc_mac *mac, const guchar *data, gsize len) { gsize i; int out_len; g_return_val_if_fail(mac != NULL, FALSE); g_return_val_if_fail(data != NULL, FALSE); for (i = 0; i < len; i++) { mac->chunk_mac[mac->position % 16] ^= data[i]; mac->position++; if (G_UNLIKELY((mac->position % 16) == 0)) { if (!EVP_EncryptUpdate(mac->ctx, mac->chunk_mac, &out_len, mac->chunk_mac, 16)) return FALSE; } // add chunk mac to the chunk macs list if we are at the chunk boundary if (G_UNLIKELY(mac->position == mac->next_boundary)) if (!chunked_cbc_mac_close_chunk(mac)) return FALSE; } return TRUE; } // on failure, the memory is freed, but mac_out can't be used static gboolean chunked_cbc_mac_finish(struct chunked_cbc_mac *mac, guchar mac_out[16]) { int out_len; g_return_val_if_fail(mac != NULL, FALSE); // finish buffer if necessary if (mac->position % 16) { while (mac->position % 16) { mac->chunk_mac[mac->position % 16] ^= 0; mac->position++; } if (!EVP_EncryptUpdate(mac->ctx, mac->chunk_mac, &out_len, mac->chunk_mac, 16)) { EVP_CIPHER_CTX_free(mac->ctx); return FALSE; } } // if there last chunk is unfinished, finish it if (mac->position > (mac->next_boundary - get_chunk_size(mac->chunk_idx))) if (!chunked_cbc_mac_close_chunk(mac)) { EVP_CIPHER_CTX_free(mac->ctx); return FALSE; } if (mac_out) memcpy(mac_out, mac->meta_mac, 16); EVP_CIPHER_CTX_free(mac->ctx); memset(mac, 0, sizeof(*mac)); return TRUE; } static gboolean chunked_cbc_mac_finish8(struct chunked_cbc_mac *mac, guchar mac_out[8]) { guchar buf[16]; gint i; g_return_val_if_fail(mac_out != NULL, FALSE); if (!chunked_cbc_mac_finish(mac, buf)) return FALSE; for (i = 0; i < 4; i++) mac_out[i] = buf[i] ^ buf[i + 4]; for (i = 0; i < 4; i++) mac_out[i + 4] = buf[i + 8] ^ buf[i + 12]; return TRUE; } // }}} // {{{ chunked CBC-MAC (simpler interface) gboolean chunk_mac_calculate(guchar iv[8], guchar key[16], const guchar *data, gsize len, guchar mac[16]) { EVP_CIPHER_CTX *ctx; const guchar *data_end; int out_len; g_return_val_if_fail(iv != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(data != NULL, FALSE); g_return_val_if_fail(len > 0, FALSE); g_return_val_if_fail(mac != NULL, FALSE); data_end = data + len; ctx = EVP_CIPHER_CTX_new(); if (!ctx) return FALSE; if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) goto err_clean; EVP_CIPHER_CTX_set_padding(ctx, 0); memcpy(mac, iv, 8); memcpy(mac + 8, iv, 8); // process data by 16 byte blocks (data is aligned) while (data + 16 < data_end) { *((guint64 *)&mac[0]) ^= *(guint64 *)(data); *((guint64 *)&mac[8]) ^= *(guint64 *)(data + 8); data += 16; if (!EVP_EncryptUpdate(ctx, mac, &out_len, mac, 16)) goto err_clean; } if (data_end - data > 0) { int i = 0; while (data_end > data) mac[i++] ^= *data++; if (!EVP_EncryptUpdate(ctx, mac, &out_len, mac, 16)) goto err_clean; } EVP_CIPHER_CTX_free(ctx); return TRUE; err_clean: EVP_CIPHER_CTX_free(ctx); return FALSE; } // meta-mac is xor of all chunk macs encrypted along the way // chunks is an ordered list of 16 byte chunk mac buffers static gboolean meta_mac_calculate(GSList *chunks, guchar key[16], guchar meta_mac[16]) { GSList *ci; EVP_CIPHER_CTX *ctx; int out_len; g_return_val_if_fail(chunks != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(meta_mac != NULL, FALSE); ctx = EVP_CIPHER_CTX_new(); if (!ctx) return FALSE; if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) goto err_clean; EVP_CIPHER_CTX_set_padding(ctx, 0); memset(meta_mac, 0, 16); for (ci = chunks; ci; ci = ci->next) { guchar *mac = ci->data; gint i; for (i = 0; i < 16; i++) meta_mac[i] ^= mac[i]; if (!EVP_EncryptUpdate(ctx, meta_mac, &out_len, meta_mac, 16)) goto err_clean; } EVP_CIPHER_CTX_free(ctx); return TRUE; err_clean: EVP_CIPHER_CTX_free(ctx); return FALSE; } #if 0 static void meta_mac_pack(guchar meta_mac[16], guchar packed[8]) { gint i; for (i = 0; i < 4; i++) packed[i] = meta_mac[i] ^ meta_mac[i + 4]; for (i = 0; i < 4; i++) packed[i + 4] = meta_mac[i + 8] ^ meta_mac[i + 12]; } #endif // }}} // {{{ aes128 ctr // data must be aligned to 16 byte block static gboolean encrypt_aes128_ctr(guchar *out, const guchar *in, gsize len, guchar key[16], guchar iv[16]) { EVP_CIPHER_CTX *ctx; int out_len; ctx = EVP_CIPHER_CTX_new(); if (!ctx) return FALSE; if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_ctr(), NULL, key, iv)) goto err_clean; EVP_CIPHER_CTX_set_padding(ctx, 0); if (!EVP_EncryptUpdate(ctx, out, &out_len, in, len)) goto err_clean; if (out_len != len) goto err_clean; EVP_CIPHER_CTX_free(ctx); return TRUE; err_clean: EVP_CIPHER_CTX_free(ctx); return FALSE; } // }}} // {{{ unpack_node_key static void unpack_node_key(guchar node_key[32], guchar aes_key[16], guchar nonce[8], guchar meta_mac_xor[8]) { if (aes_key) { DW(aes_key, 0) = DW(node_key, 0) ^ DW(node_key, 4); DW(aes_key, 1) = DW(node_key, 1) ^ DW(node_key, 5); DW(aes_key, 2) = DW(node_key, 2) ^ DW(node_key, 6); DW(aes_key, 3) = DW(node_key, 3) ^ DW(node_key, 7); } if (nonce) { DW(nonce, 0) = DW(node_key, 4); DW(nonce, 1) = DW(node_key, 5); } if (meta_mac_xor) { DW(meta_mac_xor, 0) = DW(node_key, 6); DW(meta_mac_xor, 1) = DW(node_key, 7); } } // }}} // {{{ pack_node_key static void pack_node_key(guchar node_key[32], guchar aes_key[16], guchar nonce[8], guchar meta_mac[16]) { DW(node_key, 0) = DW(aes_key, 0) ^ DW(nonce, 0); DW(node_key, 1) = DW(aes_key, 1) ^ DW(nonce, 1); DW(node_key, 2) = DW(aes_key, 2) ^ DW(meta_mac, 0) ^ DW(meta_mac, 1); DW(node_key, 3) = DW(aes_key, 3) ^ DW(meta_mac, 2) ^ DW(meta_mac, 3); DW(node_key, 4) = DW(nonce, 0); DW(node_key, 5) = DW(nonce, 1); DW(node_key, 6) = DW(meta_mac, 0) ^ DW(meta_mac, 1); DW(node_key, 7) = DW(meta_mac, 2) ^ DW(meta_mac, 3); } // }}} // {{{ encode_node_attrs static gchar *encode_node_attrs(const gchar *name) { g_return_val_if_fail(name != NULL, NULL); SJsonGen *gen = s_json_gen_new(); s_json_gen_start_object(gen); s_json_gen_member_string(gen, "n", name); s_json_gen_end_object(gen); gc_free gchar *attrs_json = s_json_gen_done(gen); return g_strdup_printf("MEGA%s", attrs_json); } // }}} // {{{ decode_node_attrs static gboolean decode_node_attrs(const gchar *attrs, gchar **name) { g_return_val_if_fail(attrs != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); // parse attributes if (!attrs || !g_str_has_prefix(attrs, "MEGA{")) return FALSE; // decode JSON if (!s_json_is_valid(attrs + 4)) return FALSE; *name = s_json_get_member_string(attrs + 4, "n"); return TRUE; } // }}} // {{{ decrypt_node_attrs static gboolean decrypt_node_attrs(const gchar *encrypted_attrs, const guchar *key, gchar **name) { g_return_val_if_fail(encrypted_attrs != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); gc_free guchar *attrs = b64_aes128_cbc_decrypt(encrypted_attrs, key, NULL); return decode_node_attrs(attrs, name); } // }}} // {{{ handle_auth gboolean handle_auth(const gchar *handle, const gchar *b64_ha, const guchar *master_key) { gsize ha_len = 0; g_return_val_if_fail(handle != NULL, FALSE); g_return_val_if_fail(b64_ha != NULL, FALSE); g_return_val_if_fail(master_key != NULL, FALSE); gc_free guchar *ha = b64_aes128_decrypt(b64_ha, master_key, &ha_len); if (!ha || ha_len != 16) return FALSE; return !memcmp(ha, handle, 8) && !memcmp(ha + 8, handle, 8); } // }}} // Server API helpers // {{{ srv_error_to_string static const gchar *srv_error_to_string(gint code) { switch (code) { case SRV_EINTERNAL: return "EINTERNAL"; case SRV_EARGS: return "EARGS"; case SRV_EAGAIN: return "EAGAIN"; case SRV_ERATELIMIT: return "ERATELIMIT"; case SRV_EFAILED: return "EFAILED"; case SRV_ETOOMANY: return "ETOOMANY"; case SRV_ERANGE: return "ERANGE"; case SRV_EEXPIRED: return "EEXPIRED"; case SRV_ENOENT: return "ENOENT"; case SRV_ECIRCULAR: return "ECIRCULAR"; case SRV_EACCESS: return "EACCESS"; case SRV_EEXIST: return "EEXIST"; case SRV_EINCOMPLETE: return "EINCOMPLETE"; case SRV_EKEY: return "EKEY"; case SRV_ESID: return "ESID"; case SRV_EBLOCKED: return "EBLOCKED"; case SRV_EOVERQUOTA: return "EOVERQUOTA"; case SRV_ETEMPUNAVAIL: return "ETEMPUNAVAIL"; case SRV_ETOOMANYCONNECTIONS: return "ETOOMANYCONNECTIONS"; default: return "EUNKNOWN"; } } // }}} // {{{ api_request_unsafe static gchar *api_request_unsafe(struct mega_session *s, const gchar *req_node, GError **err) { GError *local_err = NULL; gc_free gchar *url = NULL; g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(req_node != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); if (mega_debug & MEGA_DEBUG_API) print_node(req_node, "-> "); GString *additional_url_params = g_string_sized_new(64); GHashTableIter iter; g_hash_table_iter_init(&iter, s->api_url_params); gchar *key, *val; while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&val)) g_string_append_printf(additional_url_params, "&%s=%s", key, val); // prepare URL s->id++; if (s->sid) url = g_strdup_printf("https://g.api.mega.co.nz/cs?id=%u&sid=%s%s", s->id, s->sid, additional_url_params->str); else url = g_strdup_printf("https://g.api.mega.co.nz/cs?id=%u%s", s->id, additional_url_params->str); g_string_free(additional_url_params, TRUE); GString *res_str = http_post(s->http, url, req_node, strlen(req_node), &local_err); // handle http errors if (!res_str) { if (local_err->domain == HTTP_ERROR && (local_err->code == HTTP_ERROR_NO_RESPONSE || local_err->code == HTTP_ERROR_SERVER_BUSY)) { // simulate SRV_EAGAIN response if server drops connection return g_strdup_printf("%d", SRV_EAGAIN); } else { g_propagate_prefixed_error(err, local_err, "HTTP POST failed: "); return NULL; } } // decode JSON if (!s_json_is_valid(res_str->str)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid response JSON"); g_string_free(res_str, TRUE); return NULL; } gchar *res_node = g_string_free(res_str, FALSE); if (mega_debug & MEGA_DEBUG_API && res_node) print_node(res_node, "<- "); return res_node; } // }}} // {{{ api_request static gchar *api_request(struct mega_session *s, const gchar *req_node, GError **err) { GError *local_err = NULL; gchar *response; gint delay = 250000; // repeat after 250ms 500ms 1s ... g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(req_node != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); // some default rate limiting g_usleep(20000); again: response = api_request_unsafe(s, req_node, &local_err); if (!response) { g_propagate_error(err, local_err); return NULL; } // if we are asked to repeat the call, do it with exponential backoff if (s_json_get_type(response) == S_JSON_TYPE_NUMBER && s_json_get_int(response, SRV_EINTERNAL) == SRV_EAGAIN) { g_free(response); g_usleep(delay); delay = delay * 2; if (delay > 4 * 64 * 1000 * 1000) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Server keeps asking us for EAGAIN, giving up"); return NULL; } goto again; } return response; } // }}} // {{{ api_response_check // check that we have and array with a response value (object or error code) static const gchar *api_response_check(const gchar *response, gchar expects, gint *error_code, GError **err) { // there was already an error returned by api_request if (*err) return NULL; // null response without an error, it shouldn't happen, but handle it to be sure if (!response) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Null response"); return NULL; } if (error_code) *error_code = 0; SJsonType response_type = s_json_get_type(response); // request level error if (response_type == S_JSON_TYPE_NUMBER) { gint v = s_json_get_int(response, 0); // if it's negative, it's error status if (v < 0) { if (error_code) *error_code = v; g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Server returned error %s", srv_error_to_string(v)); return NULL; } } // check that we have and array with a response value else if (response_type == S_JSON_TYPE_ARRAY) { const gchar *node = s_json_get_element(response, 0); if (node) { SJsonType node_type = s_json_get_type(node); // we got object if (node_type == S_JSON_TYPE_OBJECT) { if (expects == 'o') return node; } else if (node_type == S_JSON_TYPE_ARRAY) { if (expects == 'a') return node; } else if (node_type == S_JSON_TYPE_NUMBER) { // we got int number gint v = s_json_get_int(node, 0); // if it's negative, it's error status if (v < 0) { if (error_code) *error_code = v; g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Server returned error %s", srv_error_to_string(v)); return NULL; } if (expects == 'i') return node; } else if (node_type == S_JSON_TYPE_BOOL) { if (expects == 'b') return node; } else if (node_type == S_JSON_TYPE_STRING) { if (expects == 's') return node; } else if (node_type == S_JSON_TYPE_NULL) { if (expects == 'n') return node; } } } g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Unexpected response"); return NULL; } // }}} // {{{ api_call static gchar *api_call(struct mega_session *s, gchar expects, gint *error_code, GError **err, const gchar *format, ...) { const gchar *node; va_list args; g_return_val_if_fail(err != NULL && *err == NULL, NULL); g_return_val_if_fail(format != NULL, NULL); va_start(args, format); gc_free gchar *request = s_json_buildv(format, args); va_end(args); if (request == NULL) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid request format: %s", format); return NULL; } gc_free gchar *response = api_request(s, request, err); node = api_response_check(response, expects, error_code, err); if (*err) { const gchar *method_node = s_json_path(request, "$[0].a!string"); if (method_node) { gc_free gchar *method = s_json_get_string(method_node); g_prefix_error(err, "API call '%s' failed: ", method); } else g_prefix_error(err, "API call failed: "); return NULL; } return s_json_get(node); } // }}} // Remote filesystem helpers // {{{ build_node_tree static void mega_node_free(struct mega_node *n); static void build_node_tree(struct mega_session *s) { GSList *i, *next; g_return_if_fail(s != NULL); // node handles are assumed to be unique GHashTable *handle_map = g_hash_table_new(g_str_hash, g_str_equal); for (i = s->fs_nodes; i; i = i->next) { struct mega_node *n = i->data; if (!g_hash_table_insert(handle_map, n->handle, n)) g_printerr("WARNING: Dup node handle detected %s\n", n->handle); } for (i = s->fs_nodes; i;) { struct mega_node *n = i->data; next = i->next; if (n->type == MEGA_NODE_CONTACT) { if (n->su_handle) n->parent = g_hash_table_lookup(handle_map, n->su_handle); } else { if (n->parent_handle) n->parent = g_hash_table_lookup(handle_map, n->parent_handle); } i = next; } g_hash_table_unref(handle_map); } // }}} // {{{ rebase_node_tree static gboolean rebase_node_tree(struct mega_session *s, const gchar *new_root) { /* Remove nodes that are not decendents of nodes with their |handle| == |specific| // ------------------------------------------------------------------------------- // Example with dir1 as the target, e.g. |handle| == |specific|: // root // / \ // / \ // dir1 dir2 // / \ \ // file1 dir3 file2 // / // file3 // // This will remove 'root', 'dir2', and 'file2'; 'dir1' becomes the root node. // -----------------------------------------------------------------------------*/ struct mega_node *root_node = mega_session_get_node_by_handle(s, new_root); if (root_node == NULL) return FALSE; if (root_node->type == MEGA_NODE_FILE) { // if the new root is a file, just place it under a newly // created root and remove everything else s->fs_nodes = g_slist_remove(s->fs_nodes, root_node); g_slist_free_full(s->fs_nodes, (GDestroyNotify)mega_node_free); s->fs_nodes = NULL; // add g_clear_pointer(&root_node->parent_handle, g_free); root_node->parent = NULL; s->fs_nodes = g_slist_prepend(s->fs_nodes, root_node); } else if (root_node->type == MEGA_NODE_FOLDER) { GSList *i, *i_next, **i_prev_next = &s->fs_nodes; GSList *free_list = NULL; g_clear_pointer(&root_node->parent_handle, g_free); root_node->parent = NULL; // find nodes that are not children of root_node and remove them for (i = s->fs_nodes; i; i = i_next) { struct mega_node *n = i->data; i_next = i->next; if (n != root_node && !mega_node_has_ancestor(n, root_node)) { // drop link *i_prev_next = i_next; g_slist_free_1(i); // we can't free the node right here, because it // needs to be available for // mega_node_has_ancestor checks free_list = g_slist_prepend(free_list, n); } else { // move next address of previously kept node i_prev_next = &i->next; } } g_slist_free_full(free_list, (GDestroyNotify)mega_node_free); } else { return FALSE; } return TRUE; } // }}} // {{{ path manipulation utils static gchar *path_sanitize_slashes(const gchar *path) { g_return_val_if_fail(path != NULL, NULL); gchar *sanepath = g_malloc(strlen(path) + 1); gchar *tmp = sanepath; gboolean previous_was_slash = 0; while (*path != '\0') { if (*path != '/' || !previous_was_slash) *(tmp++) = *path; previous_was_slash = *path == '/' ? 1 : 0; path++; } *tmp = '\0'; if (tmp > (sanepath + 1) && *(tmp - 1) == '/') *(tmp - 1) = '\0'; return sanepath; } static gchar **path_get_elements(const gchar *path) { g_return_val_if_fail(path != NULL, NULL); gc_free gchar *sane_path = path_sanitize_slashes(path); /* always succeeds */ return g_strsplit(sane_path, "/", 0); } static gchar *path_simplify(const gchar *path) { guint i, j = 0, pathv_len, subroot = 0; gboolean absolute; g_return_val_if_fail(path != NULL, NULL); gc_strfreev gchar **pathv = path_get_elements(path); /* should free */ pathv_len = g_strv_length(pathv); gc_free gchar **sane_pathv = (gchar **)g_malloc0((pathv_len + 1) * sizeof(gchar *)); absolute = (pathv_len > 1 && **pathv == '\0'); for (i = 0; i < pathv_len; i++) { if (!strcmp(pathv[i], ".")) continue; /* ignore curdirs in path */ else if (!strcmp(pathv[i], "..")) { if (absolute) { if (j > 1) { j--; } } else { if (subroot && !strcmp(sane_pathv[j - 1], "..")) /* if we are off base and last item is .. */ { sane_pathv[j++] = pathv[i]; } else { if (j > subroot) { j--; } else { subroot++; sane_pathv[j++] = pathv[i]; } } } } else { sane_pathv[j++] = pathv[i]; } } sane_pathv[j] = 0; return g_strjoinv("/", sane_pathv); } // }}} // Public API helpers // {{{ send_status // true to interrupt static void send_status(struct mega_session *s, struct mega_status_data* d) { if (s->status_callback) s->status_callback(d, s->status_userdata); } // }}} // {{{ add_share_key void add_share_key(struct mega_session *s, const gchar *handle, const guchar *key) { g_return_if_fail(s != NULL); g_return_if_fail(handle != NULL); g_return_if_fail(key != NULL); g_hash_table_insert(s->share_keys, g_strdup(handle), g_memdup2(key, 16)); } // }}} // {{{ mega_node_parse static struct mega_node *mega_node_parse(struct mega_session *s, const gchar *node) { gchar *tmp; gc_free gchar *node_h = s_json_get_member_string(node, "h"); gc_free gchar *node_p = s_json_get_member_string(node, "p"); gc_free gchar *node_u = s_json_get_member_string(node, "u"); gc_free gchar *node_k = s_json_get_member_string(node, "k"); gc_free gchar *node_a = s_json_get_member_string(node, "a"); gc_free gchar *node_sk = s_json_get_member_string(node, "sk"); gc_free gchar *node_su = s_json_get_member_string(node, "su"); gint node_t = s_json_get_member_int(node, "t", -1); gint64 node_ts = s_json_get_member_int(node, "ts", 0); gint64 node_s = s_json_get_member_int(node, "s", 0); // sanity check parsed values if (!node_h || strlen(node_h) == 0) { g_printerr("WARNING: Skipping FS node without handle\n"); return NULL; } // return special nodes if (node_t == MEGA_NODE_ROOT) { struct mega_node *n = g_new0(struct mega_node, 1); n->name = g_strdup("Root"); n->name_collate_key = g_utf8_collate_key_for_filename(n->name, -1); n->handle = g_strdup(node_h); n->timestamp = node_ts; n->type = node_t; return n; } else if (node_t == MEGA_NODE_INBOX) { struct mega_node *n = g_new0(struct mega_node, 1); n->name = g_strdup("Inbox"); n->name_collate_key = g_utf8_collate_key_for_filename(n->name, -1); n->handle = g_strdup(node_h); n->timestamp = node_ts; n->type = node_t; return n; } else if (node_t == MEGA_NODE_TRASH) { struct mega_node *n = g_new0(struct mega_node, 1); n->name = g_strdup("Trash"); n->name_collate_key = g_utf8_collate_key_for_filename(n->name, -1); n->handle = g_strdup(node_h); n->timestamp = node_ts; n->type = node_t; return n; } // allow only file and dir nodes if (node_t != MEGA_NODE_FOLDER && node_t != MEGA_NODE_FILE) { g_printerr("WARNING: Skipping FS node %s with unknown type %d\n", node_h, node_t); return NULL; } // node has to have attributes if (!node_a || strlen(node_a) == 0) { g_printerr("WARNING: Skipping FS node %s without attributes\n", node_h); return NULL; } // node has to have a key if (!node_k || strlen(node_k) == 0) { g_printerr("WARNING: Skipping FS node %s because of missing node key\n", node_h); return NULL; } // process sk if available if (node_sk && strlen(node_sk) > 0) { gsize share_key_len; gc_free guchar *share_key = NULL; if (strlen(node_sk) > 22) { share_key = b64_rsa_decrypt(node_sk, &s->rsa_key, &share_key_len); if (share_key && share_key_len >= 16) add_share_key(s, node_h, share_key); } else { share_key = b64_aes128_decrypt(node_sk, s->master_key, &share_key_len); if (share_key && share_key_len == 16) add_share_key(s, node_h, share_key); } } gchar *node_share_key = NULL; gc_free gchar *encrypted_node_key = NULL; gc_strfreev gchar **parts = g_strsplit(node_k, "/", 0); gint i; for (i = 0; parts[i]; i++) { // split node keys gchar *key_value = strchr(parts[i], ':'); if (key_value) { gchar *key_handle = parts[i]; *key_value = '\0'; key_value++; if (s->user_handle && !strcmp(s->user_handle, key_handle)) { // we found a key encrypted by me encrypted_node_key = g_strdup(key_value); node_share_key = s->master_key; break; } node_share_key = g_hash_table_lookup(s->share_keys, key_handle); if (node_share_key) { encrypted_node_key = g_strdup(key_value); } } } if (!encrypted_node_key) { g_printerr("WARNING: Skipping FS node %s because node key wasn't found\n", node_h); return NULL; } // keys longer than 45 chars are RSA keys if (strlen(encrypted_node_key) >= 46) { g_printerr("WARNING: Skipping FS node %s because it has RSA key\n", node_h); return NULL; } // decrypt node key gsize node_key_len = 0; gc_free guchar *node_key = b64_aes128_decrypt(encrypted_node_key, node_share_key, &node_key_len); if (!node_key) { g_printerr("WARNING: Skipping FS node %s because key can't be decrypted %s\n", node_h, encrypted_node_key); return NULL; } if (node_t == MEGA_NODE_FILE && node_key_len != 32) { g_printerr("WARNING: Skipping FS node %s because file key doesn't have 32 bytes\n", node_h); return NULL; } if (node_t == MEGA_NODE_FOLDER && node_key_len != 16) { g_printerr("WARNING: Skipping FS node %s because folder key doesn't have 16 bytes\n", node_h); return NULL; } // decrypt attributes with node key guchar aes_key[16]; if (node_t == MEGA_NODE_FILE) unpack_node_key(node_key, aes_key, NULL, NULL); else memcpy(aes_key, node_key, 16); gc_free gchar *node_name = NULL; if (!decrypt_node_attrs(node_a, aes_key, &node_name)) { g_printerr("WARNING: Skipping FS node %s because it has malformed attributes\n", node_h); return NULL; } if (!node_name) { g_printerr("WARNING: Skipping FS node %s because it is missing name\n", node_h); return NULL; } // replace invalid filename characters with whitespace gchar *check = node_name; #ifdef G_OS_WIN32 while ((check = strpbrk(check, "/\\<>:\"|?*"))) #else while ((check = strpbrk(check, "/"))) #endif *check = '_'; // check for invalid names if (!strcmp(node_name, ".") || !strcmp(node_name, "..")) { g_printerr("WARNING: Skipping FS node %s because it's name is invalid '%s'\n", node_h, node_name); return NULL; } #define TAKE(n) (tmp = n, n = NULL, tmp) struct mega_node *n = g_new0(struct mega_node, 1); n->s = s; n->name = TAKE(node_name); n->name_collate_key = g_utf8_collate_key_for_filename(n->name, -1); n->handle = TAKE(node_h); n->parent_handle = TAKE(node_p); n->user_handle = TAKE(node_u); n->su_handle = TAKE(node_su); n->key_len = node_key_len; n->key = TAKE(node_key); n->size = node_s; n->timestamp = node_ts; n->type = node_t; return n; } // }}} // {{{ mega_node_parse_user static struct mega_node *mega_node_parse_user(struct mega_session *s, const gchar *node) { gc_free gchar *node_u = s_json_get_member_string(node, "u"); gc_free gchar *node_m = s_json_get_member_string(node, "m"); gint64 node_ts = s_json_get_member_int(node, "ts", 0); // sanity check parsed values if (!node_u || strlen(node_u) == 0) return NULL; if (!node_m || strlen(node_m) == 0) return NULL; struct mega_node *n = g_new0(struct mega_node, 1); n->s = s; n->name = node_m; n->name_collate_key = g_utf8_collate_key_for_filename(n->name, -1); n->handle = node_u; n->parent_handle = g_strdup("NETWORK"); n->user_handle = g_strdup(node_u); n->timestamp = node_ts; n->type = MEGA_NODE_CONTACT; node_u = node_m = NULL; return n; } // }}} // {{{ mega_node_is_writable gboolean mega_node_is_writable(struct mega_session *s, struct mega_node *n) { g_return_val_if_fail(n != NULL, FALSE); return n->type == MEGA_NODE_CONTACT || ((n->type == MEGA_NODE_FILE || n->type == MEGA_NODE_FOLDER) && !strcmp(s->user_handle, n->user_handle)) || n->type == MEGA_NODE_ROOT || n->type == MEGA_NODE_NETWORK || n->type == MEGA_NODE_TRASH; } // }}} // {{{ mega_node_free static void mega_node_free(struct mega_node *n) { if (n) { g_free(n->name); g_free(n->name_collate_key); g_free(n->handle); g_free(n->parent_handle); g_free(n->user_handle); g_free(n->su_handle); g_free(n->key); g_free(n->link); memset(n, 0, sizeof(struct mega_node)); g_free(n); } } // }}} // Public API // {{{ mega_error_quark GQuark mega_error_quark(void) { return g_quark_from_static_string("mega-error-quark"); } // }}} // {{{ mega_session_new struct mega_session *mega_session_new(void) { struct mega_session *s = g_new0(struct mega_session, 1); s->http = http_new(); http_set_content_type(s->http, "application/json"); s->id = time(NULL); s->rid = make_request_id(); s->api_url_params = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); s->share_keys = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); s->resume_enabled = TRUE; return s; } // }}} // {{{ mega_session_set_speed void mega_session_set_speed(struct mega_session *s, gint ul, gint dl) { g_return_if_fail(s != NULL); s->max_ul = ul; s->max_dl = dl; } // }}} // {{{ mega_session_set_workers void mega_session_set_workers(struct mega_session *s, gint workers) { g_return_if_fail(s != NULL); s->max_workers = workers; } // }}} // {{{ mega_session_set_proxy void mega_session_set_proxy(struct mega_session *s, const gchar *proxy) { g_return_if_fail(s != NULL); g_free(s->proxy); s->proxy = g_strdup(proxy); http_set_proxy(s->http, s->proxy); } // }}} // {{{ mega_session_set_resume void mega_session_set_resume(struct mega_session *s, gboolean enabled) { g_return_if_fail(s != NULL); s->resume_enabled = enabled; } // }}} // {{{ mega_session_free void mega_session_free(struct mega_session *s) { if (s) { http_free(s->http); g_slist_free_full(s->fs_nodes, (GDestroyNotify)mega_node_free); g_hash_table_destroy(s->share_keys); g_hash_table_destroy(s->api_url_params); g_free(s->sid); g_free(s->rid); g_free(s->password_key); g_free(s->master_key); rsa_key_free(&s->rsa_key); g_free(s->user_handle); g_free(s->user_name); g_free(s->user_email); memset(s, 0, sizeof(struct mega_session)); g_free(s); } } // }}} // {{{ mega_session_watch_status void mega_session_watch_status(struct mega_session *s, mega_status_callback cb, gpointer userdata) { g_return_if_fail(s != NULL); s->status_callback = cb; s->status_userdata = userdata; } // }}} // {{{ mega_session_enable_previews void mega_session_enable_previews(struct mega_session *s, gboolean enable) { g_return_if_fail(s != NULL); s->create_preview = enable; } // }}} // {{{ mega_session_open_exp_folder // |specific| is optional so it can be NULL gboolean mega_session_open_exp_folder(struct mega_session *s, const gchar *n, const gchar *key, const gchar *specific, GError **err) { GError *local_err = NULL; gsize len, i, l; GSList *list = NULL; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(n != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); g_hash_table_replace(s->api_url_params, g_strdup("n"), g_strdup(n)); g_free(s->master_key); s->master_key = base64urldecode(key, &len); if (len != 16) return FALSE; // login user gc_free gchar *f_node = api_call(s, 'o', NULL, &local_err, "[{a:f, c:1, r:1}]"); if (!f_node) { g_propagate_error(err, local_err); return FALSE; } const gchar *ff_node = s_json_get_member(f_node, "f"); if (ff_node && s_json_get_type(ff_node) == S_JSON_TYPE_ARRAY) { gc_free gchar** f_elems = s_json_get_elements(ff_node); gchar** f_elem = f_elems; while (*f_elem) { if (s_json_get_type(*f_elem) == S_JSON_TYPE_OBJECT) { // first node is the root folder if (f_elem == f_elems) { gc_free gchar *node_h = s_json_get_member_string(*f_elem, "h"); add_share_key(s, node_h, s->master_key); } // import nodes into the fs struct mega_node *n = mega_node_parse(s, *f_elem); if (n) { if (f_elem == f_elems) { g_free(n->parent_handle); n->parent_handle = NULL; } list = g_slist_prepend(list, n); } } f_elem++; } } g_slist_free_full(s->fs_nodes, (GDestroyNotify)mega_node_free); s->fs_nodes = g_slist_reverse(list); build_node_tree(s); // rebase node tree if (specific) { if (!rebase_node_tree(s, specific)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Node not found: %s", specific); return FALSE; } } return TRUE; } // }}} // {{{ mega_session_open // this has side effect of the current session being closed static gboolean mega_session_load(struct mega_session *s, const gchar *un, const gchar *pw, gint max_age, gchar **last_sid, gchar** last_pwsalt_v2, GError **err); gboolean mega_session_open(struct mega_session *s, const gchar *un, const gchar *pw, gint max_age, gboolean *is_new_session, GError **err) { GError *local_err = NULL; gboolean is_loggedin = FALSE; gc_free gchar* sid = NULL; gc_free gchar* pwsalt_v2 = NULL; gint login_variant = 0; gc_free gchar *un_lower = NULL; gc_free gchar *uh = NULL; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(un != NULL, FALSE); g_return_val_if_fail(pw != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); // try to load cached session data first if (mega_session_load(s, un, pw, max_age, &sid, &pwsalt_v2, NULL)) { *is_new_session = FALSE; return TRUE; } *is_new_session = TRUE; // session load failed, clean session data mega_session_close(s); s->password_key_save = make_password_key(pw); un_lower = g_ascii_strdown(un, -1); // now if mega_session_load found a previous expired cache file, it will // return sid and pwsalt_v2 that we can try to re-use if (sid) { // if load_session returned sid, existence of pwsalt_v2 is // enough to determine login variant login_variant = pwsalt_v2 ? 2 : 0; g_free(s->sid); s->sid = g_strdup(sid); g_free(s->password_salt_v2); s->password_salt_v2 = g_strdup(pwsalt_v2); } else { // no previous session data found, we need to call us0 to determine the login // variant in use gc_free gchar *pre_login_node = api_call(s, 'o', NULL, &local_err, "[{a:us0, user:%s}]", un_lower); if (!pre_login_node) { g_propagate_error(err, local_err); return FALSE; } login_variant = s_json_get_member_int(pre_login_node, "v", 0); if (login_variant == 2) { // get the password salt for the login v2 g_free(pwsalt_v2); pwsalt_v2 = s_json_get_member_string(pre_login_node, "s"); if (pwsalt_v2 == NULL) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Missing salt (v2)"); return FALSE; } g_free(s->password_salt_v2); s->password_salt_v2 = g_strdup(pwsalt_v2); } } // session is not valid or is missing, we need to login again via 'us' // call if (login_variant == 2) { // login variant 2: // - uses PKCS5_PBKDF2_HMAC which requires a salt that is generated during registration // and stored on the server (also returned by 'us0' call in the 's' field) // - PKCS5_PBKDF2_HMAC produces a 32byte value: // - first half is a password key // - second half is user hash for the 'us' call // decode salt from base64 gsize salt_len; gc_free gchar *salt = base64urldecode(pwsalt_v2, &salt_len); if (salt == NULL) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Failed to decode salt (v2)"); return FALSE; } // derive password key and user hash guchar key[32]; PKCS5_PBKDF2_HMAC(pw, strlen(pw), salt, salt_len, 100000, EVP_sha512(), sizeof(key), key); g_free(s->password_key); s->password_key = g_memdup2(key, 16); uh = base64urlencode(key + 16, 16); } else { // make password key and user hash for v1 login g_free(s->password_key); s->password_key = make_password_key(pw); uh = make_username_hash(un_lower, s->password_key); } if (uh == NULL) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Faield to encode user hash"); return FALSE; } if (sid) { // try to fetch user data, this will verify the session id if (mega_session_get_user(s, NULL)) return TRUE; g_free(s->sid); s->sid = NULL; } // login user gc_free gchar *login_node = api_call(s, 'o', NULL, &local_err, "[{a:us, user:%s, uh:%s}]", un_lower, uh); if (!login_node) { g_propagate_error(err, local_err); return FALSE; } gc_free gchar *login_k = s_json_get_member_string(login_node, "k"); gc_free gchar *login_privk = s_json_get_member_string(login_node, "privk"); gc_free gchar *login_csid = s_json_get_member_string(login_node, "csid"); // decrypt master key gc_free guchar *master_key = b64_aes128_decrypt(login_k, s->password_key, NULL); if (!master_key) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't read master key during login"); return FALSE; } // decrypt private key with master key struct rsa_key privk; memset(&privk, 0, sizeof(privk)); if (!b64_aes128_decrypt_privk(login_privk, master_key, &privk)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't read private key during login"); rsa_key_free(&privk); return FALSE; } // decrypt session id gsize sid_len = 0; gc_free guchar *sid_binary = b64_rsa_decrypt(login_csid, &privk, &sid_len); if (!sid_binary || sid_len < 43) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't read session id during login"); rsa_key_free(&privk); return FALSE; } // save session id g_free(s->sid); s->sid = base64urlencode(sid_binary, 43); // cleanup rsa_key_free(&privk); return mega_session_get_user(s, err); } // }}} // {{{ mega_session_close void mega_session_close(struct mega_session *s) { g_return_if_fail(s != NULL); g_free(s->password_key); g_free(s->password_key_save); g_free(s->password_salt_v2); g_free(s->master_key); g_free(s->sid); rsa_key_free(&s->rsa_key); g_free(s->user_handle); g_free(s->user_name); g_free(s->user_email); g_slist_free_full(s->fs_nodes, (GDestroyNotify)mega_node_free); g_hash_table_remove_all(s->share_keys); g_hash_table_remove_all(s->api_url_params); s->password_key = NULL; s->password_key_save = NULL; s->password_salt_v2 = NULL; s->master_key = NULL; s->sid = NULL; s->user_handle = NULL; s->user_email = NULL; s->user_name = NULL; s->fs_nodes = NULL; s->last_refresh = 0; s->status_callback = NULL; } // }}} // {{{ mega_session_get_sid const gchar *mega_session_get_sid(struct mega_session *s) { g_return_val_if_fail(s != NULL, NULL); return s->sid; } // }}} // {{{ mega_session_get_user gboolean mega_session_get_user(struct mega_session *s, GError **err) { GError *local_err = NULL; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(s->sid != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); // prepare request gc_free gchar *user_node = api_call(s, 'o', NULL, &local_err, "[{a:ug}]"); if (!user_node) { g_propagate_error(err, local_err); return FALSE; } // store information about the user g_free(s->user_handle); s->user_handle = s_json_get_member_string(user_node, "u"); if (!s->user_handle) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't read user's handle"); return FALSE; } g_free(s->user_email); s->user_email = s_json_get_member_string(user_node, "email"); g_free(s->user_name); s->user_name = s_json_get_member_string(user_node, "name"); gc_free gchar *user_privk = s_json_get_member_string(user_node, "privk"); gc_free gchar *user_pubk = s_json_get_member_string(user_node, "pubk"); gc_free gchar *user_k = s_json_get_member_string(user_node, "k"); // load master key g_free(s->master_key); s->master_key = b64_aes128_decrypt(user_k, s->password_key, NULL); if (!s->master_key) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't read master key"); return FALSE; } rsa_key_free(&s->rsa_key); // decrypt private key with master key if (!b64_aes128_decrypt_privk(user_privk, s->master_key, &s->rsa_key)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't read private key"); return FALSE; } // load public key if (!b64_decode_pubk(user_pubk, &s->rsa_key)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't read public key"); return FALSE; } s->last_refresh = time(NULL); return TRUE; } // }}} // {{{ mega_session_refresh gboolean mega_session_refresh(struct mega_session *s, GError **err) { GError *local_err = NULL; GSList *list = NULL; gint i, l; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); // prepare request gc_free gchar *f_node = api_call(s, 'o', NULL, &local_err, "[{a:f, c:1}]"); if (!f_node) { g_propagate_error(err, local_err); return FALSE; } if (mega_debug & MEGA_DEBUG_FS) print_node(f_node, "FS: "); // process 'ok' array const gchar *ok_node = s_json_get_member(f_node, "ok"); if (ok_node && s_json_get_type(ok_node) == S_JSON_TYPE_ARRAY) { gc_free gchar **oks = s_json_get_elements(ok_node); for (i = 0, l = g_strv_length(oks); i < l; i++) { const gchar *ok = oks[i]; if (s_json_get_type(ok) != S_JSON_TYPE_OBJECT) continue; gc_free gchar *ok_h = s_json_get_member_string(ok, "h"); // h.8 gc_free gchar *ok_ha = s_json_get_member_string(ok, "ha"); // b64(aes(h.8 h.8, master_key)) gc_free gchar *ok_k = s_json_get_member_string(ok, "k"); // b64(aes(share_key_for_h, master_key)) if (!ok_h || !ok_ha || !ok_k) { g_printerr( "WARNING: Skipping import of a key %s because it's missing required attributes\n", ok_h); continue; } if (!handle_auth(ok_h, ok_ha, s->master_key)) { g_printerr("WARNING: Skipping import of a key %s because it's authentication failed\n", ok_h); continue; } //g_print("Importing key %s:%s\n", ok_h, ok_k); gc_free guchar *key = b64_aes128_decrypt(ok_k, s->master_key, NULL); add_share_key(s, ok_h, key); } } // process 'f' array const gchar *ff_node = s_json_get_member(f_node, "f"); if (!ff_node || s_json_get_type(ff_node) != S_JSON_TYPE_ARRAY) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Remote filesystem 'f' node is invalid"); return FALSE; } gc_free gchar **ff_arr = s_json_get_elements(ff_node); for (i = 0, l = g_strv_length(ff_arr); i < l; i++) { const gchar *f = ff_arr[i]; if (s_json_get_type(f) != S_JSON_TYPE_OBJECT) continue; struct mega_node *n = mega_node_parse(s, f); if (n) list = g_slist_prepend(list, n); } // import special root node for contacts struct mega_node *n = g_new0(struct mega_node, 1); n->s = s; n->name = g_strdup("Contacts"); n->name_collate_key = g_utf8_collate_key_for_filename(n->name, -1); n->handle = g_strdup("NETWORK"); n->type = MEGA_NODE_NETWORK; list = g_slist_prepend(list, n); // process 'u' array const gchar *u_node = s_json_get_member(f_node, "u"); if (u_node && s_json_get_type(u_node) == S_JSON_TYPE_ARRAY) { gc_free gchar **u_arr = s_json_get_elements(u_node); for (i = 0, l = g_strv_length(u_arr); i < l; i++) { const gchar *u = u_arr[i]; if (s_json_get_type(u) != S_JSON_TYPE_OBJECT) continue; gint64 u_c = s_json_get_member_int(u, "c", 0); // skip self and removed if (u_c != 1) continue; struct mega_node *n = mega_node_parse_user(s, u); if (n) list = g_slist_prepend(list, n); } } // replace existing nodes g_slist_free_full(s->fs_nodes, (GDestroyNotify)mega_node_free); s->fs_nodes = g_slist_reverse(list); build_node_tree(s); s->last_refresh = time(NULL); return TRUE; } // }}} // {{{ mega_session_addlinks gboolean mega_session_addlinks(struct mega_session *s, GSList *nodes, GError **err) { GError *local_err = NULL; GSList *i; gc_ptr_array_unref GPtrArray *rnodes = NULL; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); if (g_slist_length(nodes) == 0) return TRUE; rnodes = g_ptr_array_sized_new(g_slist_length(nodes)); // prepare request SJsonGen *gen = s_json_gen_new(); s_json_gen_start_array(gen); for (i = nodes; i; i = i->next) { struct mega_node *n = i->data; if (n->type == MEGA_NODE_FILE) { s_json_gen_start_object(gen); s_json_gen_member_string(gen, "a", "l"); s_json_gen_member_string(gen, "n", n->handle); s_json_gen_end_object(gen); g_ptr_array_add(rnodes, n); } } s_json_gen_end_array(gen); gc_free gchar *request = s_json_gen_done(gen); // perform request gc_free gchar *response = api_request(s, request, &local_err); // process response if (!response) { g_propagate_prefixed_error(err, local_err, "API call 'l' failed: "); return FALSE; } if (s_json_get_type(response) == S_JSON_TYPE_ARRAY) { gc_free gchar **nodes_arr = s_json_get_elements(response); gint i, l = g_strv_length(nodes_arr); if (l != rnodes->len) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "API call 'l' results mismatch"); return FALSE; } for (i = 0; i < l; i++) { gchar *link = s_json_get_string(nodes_arr[i]); struct mega_node *n = g_ptr_array_index(rnodes, i); g_free(n->link); n->link = link; } } return TRUE; } // }}} // {{{ mega_session_user_quota struct mega_user_quota *mega_session_user_quota(struct mega_session *s, GError **err) { GError *local_err = NULL; g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); // prepare request gc_free gchar *quota_node = api_call(s, 'o', NULL, &local_err, "[{a:uq, strg:1, xfer:1, pro:1}]"); if (!quota_node) { g_propagate_error(err, local_err); return NULL; } struct mega_user_quota *q = g_new0(struct mega_user_quota, 1); q->total = s_json_get_member_int(quota_node, "mstrg", 0); q->used = s_json_get_member_int(quota_node, "cstrg", 0); return q; } // }}} // {{{ mega_session_ls_all // free gslist, not the data GSList *mega_session_ls_all(struct mega_session *s) { GSList *list = NULL; g_return_val_if_fail(s != NULL, NULL); return g_slist_copy(s->fs_nodes); } // }}} // {{{ mega_session_ls struct ls_data { GSList *list; gchar *path; gboolean recursive; }; static void _ls(struct mega_node *n, struct ls_data *data) { gchar path[4096]; if (mega_node_get_path(n, path, sizeof(path))) { if (g_str_has_prefix(path, data->path) && (data->recursive || !strchr(path + strlen(data->path), '/'))) data->list = g_slist_prepend(data->list, n); } } // free gslist, not the data GSList *mega_session_ls(struct mega_session *s, const gchar *path, gboolean recursive) { struct ls_data data; g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(path != NULL, NULL); gc_free gchar *tmp = path_simplify(path); if (!strcmp(tmp, "/")) data.path = g_strdup("/"); else data.path = g_strdup_printf("%s/", tmp); data.recursive = recursive; data.list = NULL; g_slist_foreach(s->fs_nodes, (GFunc)_ls, &data); g_free(data.path); return data.list; } // }}} // {{{ mega_session_stat struct mega_node *mega_session_stat(struct mega_session *s, const gchar *path) { g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(path != NULL, NULL); gc_free gchar *tmp = path_simplify(path); GSList *iter; gchar n_path[4096]; for (iter = s->fs_nodes; iter; iter = iter->next) { struct mega_node *n = iter->data; if (mega_node_get_path(n, n_path, sizeof(n_path))) { if (!strcmp(n_path, tmp)) return n; } } return NULL; } // }}} // {{{ mega_session_get_node_chilren static gint node_name_compare(const struct mega_node *a, const struct mega_node *b) { if (a->name_collate_key == NULL && b->name_collate_key == NULL) return 0; if (a->name_collate_key == NULL) return -1; if (b->name_collate_key == NULL) return 1; return strcmp(a->name_collate_key, b->name_collate_key); } GSList *mega_session_get_node_chilren(struct mega_session *s, struct mega_node *node) { GSList *list = NULL, *i; g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(node != NULL, NULL); g_return_val_if_fail(node->handle != NULL, NULL); for (i = s->fs_nodes; i; i = i->next) { struct mega_node *child = i->data; if (child->parent_handle && !strcmp(child->parent_handle, node->handle)) list = g_slist_insert_sorted(list, child, (GCompareFunc)node_name_compare); } return list; } // }}} // {{{ mega_session_mkdir struct mega_node *mega_session_mkdir(struct mega_session *s, const gchar *path, GError **err) { GError *local_err = NULL; struct mega_node *n = NULL; g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(path != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); struct mega_node *d = mega_session_stat(s, path); if (d) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Directory already exists: %s", path); return NULL; } gc_free gchar *tmp = path_simplify(path); gc_free gchar *parent_path = g_path_get_dirname(tmp); if (!strcmp(parent_path, "/")) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't create toplevel dir: %s", path); return NULL; } struct mega_node *p = mega_session_stat(s, parent_path); if (!p || p->type == MEGA_NODE_FILE || p->type == MEGA_NODE_INBOX) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Parent directory doesn't exist: %s", parent_path); return NULL; } if (!mega_node_is_writable(s, p)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Parent directory is not writable: %s", parent_path); return NULL; } if (p->type == MEGA_NODE_NETWORK) { // prepare contact add request gc_free gchar *ur_node = api_call(s, 'o', NULL, &local_err, "[{a:ur, u:%S, l:1, i:%s}]", g_path_get_basename(tmp), s->rid); if (!ur_node) { g_propagate_error(err, local_err); return NULL; } // parse response n = mega_node_parse_user(s, ur_node); if (!n) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid response"); return NULL; } } else { gc_free guchar *node_key = make_random_key(); gc_free gchar *basename = g_path_get_basename(tmp); gc_free gchar *attrs = encode_node_attrs(basename); gc_free gchar *dir_attrs = b64_aes128_cbc_encrypt_str(attrs, node_key); gc_free gchar *dir_key = b64_aes128_encrypt(node_key, 16, s->master_key); // prepare request gc_free gchar *mkdir_node = api_call(s, 'o', NULL, &local_err, "[{a:p, t:%s, i:%s, n: [{h:xxxxxxxx, t:1, k:%s, a:%s}]}]", p->handle, s->rid, dir_key, dir_attrs); if (!mkdir_node) { g_propagate_error(err, local_err); return NULL; } const gchar *f_arr = s_json_get_member(mkdir_node, "f"); if (s_json_get_type(f_arr) != S_JSON_TYPE_ARRAY) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid response"); return NULL; } const gchar *f_el = s_json_get_element(f_arr, 0); if (!f_el || s_json_get_type(f_el) != S_JSON_TYPE_OBJECT) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid response"); return NULL; } n = mega_node_parse(s, f_el); if (!n) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid response"); return NULL; } } // add mkdired node to the filesystem s->fs_nodes = g_slist_append(s->fs_nodes, n); build_node_tree(s); return n; } // }}} // {{{ mega_session_rm gboolean mega_session_rm(struct mega_session *s, const gchar *path, GError **err) { GError *local_err = NULL; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(path != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); struct mega_node *mn = mega_session_stat(s, path); if (!mn) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "File not found: %s", path); return FALSE; } if (!mega_node_is_writable(s, mn)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "File is not removable: %s", path); return FALSE; } if (mn->type == MEGA_NODE_FILE || mn->type == MEGA_NODE_FOLDER) { // prepare request gc_free gchar *rm_node = api_call(s, 'i', NULL, &local_err, "[{a:d, i:%s, n:%s}]", s->rid, mn->handle); if (!rm_node) { g_propagate_error(err, local_err); return FALSE; } } else if (mn->type == MEGA_NODE_CONTACT) { gc_free gchar *ur_node = api_call(s, 'i', NULL, &local_err, "[{a:ur, u:%s, l:0, i:%s}]", mn->handle, s->rid); if (!ur_node) { g_propagate_error(err, local_err); return FALSE; } } else { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't remove system dir %s", path); return FALSE; } GSList *i, *i_next, **i_prev_next = &s->fs_nodes; GSList *free_list = NULL; // remove node and all the children from the list for (i = s->fs_nodes; i; i = i_next) { struct mega_node *n = i->data; i_next = i->next; if (n == mn || mega_node_has_ancestor(n, mn)) { // drop link *i_prev_next = i_next; g_slist_free_1(i); // we can't free the node right here, because it // needs to be available for // mega_node_has_ancestor checks free_list = g_slist_prepend(free_list, n); } else { // move next address of previously kept node i_prev_next = &i->next; } } g_slist_free_full(free_list, (GDestroyNotify)mega_node_free); return TRUE; } // }}} // {{{ mega_session_new_node_attribute gchar *mega_session_new_node_attribute(struct mega_session *s, const guchar *data, gsize len, const gchar *type, const guchar *key, GError **err) { GError *local_err = NULL; AES_KEY k; guchar iv[AES_BLOCK_SIZE] = { 0 }; gsize pad = len % 16 ? 16 - (len % 16) : 0; g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(data != NULL, NULL); g_return_val_if_fail(len > 0, NULL); g_return_val_if_fail(type != NULL, NULL); g_return_val_if_fail(key != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); gc_free gchar *ufa_node = api_call(s, 'o', NULL, &local_err, "[{a:ufa, s:%i, ssl:0}]", (gint64)len + pad); if (!ufa_node) { g_propagate_error(err, local_err); return NULL; } gc_free gchar *p_url = s_json_get_member_string(ufa_node, "p"); // encrypt AES_set_encrypt_key(key, 128, &k); gc_free guchar *plain = g_memdup2(data, len); plain = g_realloc(plain, len + pad); memset(plain + len, 0, pad); gc_free guchar *cipher = g_malloc0(len + pad); AES_cbc_encrypt(plain, cipher, len + pad, &k, iv, 1); // upload gc_http_free struct http *h = http_new(); http_set_proxy(h, s->proxy); http_set_content_type(h, "application/octet-stream"); gc_string_free GString *handle = http_post(h, p_url, cipher, len + pad, &local_err); if (!handle) { g_propagate_prefixed_error(err, local_err, "Node attribute data upload failed: "); return NULL; } if (handle->len != 8) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Node attribute handle is invalid"); return NULL; } gc_free gchar *b64_handle = base64urlencode(handle->str, handle->len); return g_strdup_printf("%s*%s", type, b64_handle); } // }}} // {{{ Data transfer manager // --------------------- // // ATM this supports parallel chunked transfer of data without breaking the // assumptions of the original code about upload/downlad call being a blocking // operation. // // How it works: // // - It uses threaded design where threads communicate by passing messages to // each other. Threads never modify other threads' data while the other // threads are running. // - Main thread issues transfer requests to the transfer manager thread and // waits for messages about progress/completion on the submitter_mailbox, // which is part of the transfer. When it gets a message about transfer // completion it returns. // - Manager thread picks up a manager_mailbox and processes transfer // submissions from the main thread, prepares incomming transfer submissions // by splitting transfers into chunks and by keeping the list of queued // transfers. It also schedules chunk transfers by passing chunks for // actual download or upload to the worker pool. It also listens for messages // about chunk status changes from the worker threads and schedules // new trasnfers or processes transfer completions. // - Worker threads are started to download or upload the chunk and report // the progress and the final result to the manager. // // Memory: // // - Messages are allocated by the sender and freed by the receiver. // - Transfer is allocated on the stack of the main thread ATM. // - Chunks are allocated and freed by the manager thread. // - Worker threads allocate and free the http client and temporary // buffer for the chunk data. // // Locking: // // - It is necessary to serialize access to the file data stream via stream_lock. // - Workers need to sycnhronize access to transfer data: // - upload_handle // - transfered_size // - Other than that, all synchronization is handled by the message queues. #define tman_debug(fmt, args...) \ G_STMT_START \ { \ if (mega_debug & MEGA_DEBUG_TMAN) \ g_print("%" G_GINT64_FORMAT ": " fmt, g_get_monotonic_time(), ##args); \ } \ G_STMT_END enum { CHUNK_STATE_QUEUED = 0, CHUNK_STATE_IN_PROGRESS, CHUNK_STATE_DONE, }; struct transfer_chunk_mac { guchar mac[16]; guint off; guint size; }; struct transfer_chunk { gint status; gint failures_count; // number of failures when retrying struct transfer *transfer; goffset offset; goffset size; goffset transfered_size; guint index; gint64 start_at; int n_macs; struct transfer_chunk_mac macs[]; }; enum { TRANSFER_WORKER_MSG_STOP = 1, TRANSFER_WORKER_MSG_UPLOAD_CHUNK, }; struct transfer_worker_msg { gint type; struct transfer_chunk* chunk; //GError *error; }; struct transfer_worker { gint index; gboolean busy; GThread* thread; GAsyncQueue* mailbox; }; enum { TRANSFER_MSG_DONE = 1, TRANSFER_MSG_PROGRESS, TRANSFER_MSG_ERROR, }; struct transfer_msg { gint type; gchar *upload_handle; guchar meta_mac[16]; goffset total_size; goffset transfered_size; GError *error; }; struct transfer { // queue for sending mesages to a submitter GAsyncQueue *submitter_mailbox; GSList *chunks; goffset total_size; goffset transfered_size; AES_KEY k; guchar file_key[16]; guchar nonce[16]; // file access is serialized with this mutex GMutex stream_lock; // for upload GFileInputStream *istream; const gchar *upload_url; gchar *upload_handle; // for download GFileOutputStream *ostream; const gchar *download_url; // HTTP options gint max_ul; gint max_dl; gchar *proxy; // transfer is in error state, will be aborted GError *error; }; enum { TRANSFER_MANAGER_MSG_SUBMIT_TRANSFER = 1, TRANSFER_MANAGER_MSG_CHUNK_PROGRESS, TRANSFER_MANAGER_MSG_CHUNK_FAILED, TRANSFER_MANAGER_MSG_CHUNK_DONE, TRANSFER_MANAGER_MSG_STOP, }; struct transfer_manager_msg { gint type; struct transfer *transfer; struct transfer_chunk *chunk; struct transfer_worker* worker; goffset transfered_size; // how many bytes of a chunk have been transfered gchar *upload_handle; // can be sent with the last chunk update GError *error; }; struct transfer_manager { GAsyncQueue *manager_mailbox; GThread *manager_thread; // only manager thread accesses these after it is started: struct transfer_worker *workers; int max_workers; int current_workers; GList *transfers; }; static struct transfer_manager tman; static gboolean tman_transfer_progress(goffset dltotal, goffset dlnow, goffset ultotal, goffset ulnow, gpointer user_data) { struct transfer_chunk *c = user_data; struct transfer_manager_msg *msg; //tman_debug("W: progress for chunk %d (status=%d)\n", c->index, c->status); msg = g_new0(struct transfer_manager_msg, 1); msg->type = TRANSFER_MANAGER_MSG_CHUNK_PROGRESS; msg->chunk = c; msg->transfered_size = MIN(ulnow, c->size); g_async_queue_push(tman.manager_mailbox, msg); return TRUE; } static gchar* upload_checksum(const guchar* data, gsize len) { guchar crc[12] = {0}; gsize i; for (i = 0; i < len; i++) crc[i % sizeof crc] ^= data[i]; return base64urlencode(crc, sizeof crc); } // accesses: // - chunk: size, index, offset, mac // - transfer: stream_lock, istream, nonce, file_key, upload_url, max_ul, max_dl, proxy static void tman_worker_upload_chunk(struct transfer_chunk *c, struct transfer_worker* worker, struct http* h) { struct transfer *t = c->transfer; struct transfer_manager_msg *msg; gsize bytes_read; GError *err = NULL; GError *local_err = NULL; gc_free gchar *url = NULL; gc_string_free GString *response = NULL; gc_free gchar* chksum = NULL; tman_debug("W[%d]: started for chunk %d\n", worker->index, c->index); // load plaintext data into a buffer from the file gc_free guchar *buf = g_malloc(c->size); g_mutex_lock(&t->stream_lock); if (!g_seekable_seek(G_SEEKABLE(t->istream), c->offset, G_SEEK_SET, NULL, &local_err)) { g_mutex_unlock(&t->stream_lock); err = g_error_new(MEGA_ERROR, MEGA_ERROR_OTHER, "Failed seeking in the stream: %s", local_err->message); g_clear_error(&local_err); goto err; } if (!g_input_stream_read_all(G_INPUT_STREAM(t->istream), buf, c->size, &bytes_read, NULL, &local_err)) { g_mutex_unlock(&t->stream_lock); err = g_error_new(MEGA_ERROR, MEGA_ERROR_OTHER, "Failed reading from the stream: %s", local_err->message); g_clear_error(&local_err); goto err; } g_mutex_unlock(&t->stream_lock); if (bytes_read != c->size) { err = g_error_new(MEGA_ERROR, MEGA_ERROR_OTHER, "Failed reading from the stream (premature end)"); goto err; } // perform encryption and chunk mac calculation guchar iv[AES_BLOCK_SIZE] = { 0 }; memcpy(iv, t->nonce, 8); *((guint64 *)&iv[8]) = GUINT64_TO_BE((guint64)(c->offset / 16)); // this is ok, because chunks are 16b aligned for (int i = 0; i < c->n_macs; i++) chunk_mac_calculate(t->nonce, t->file_key, buf + c->macs[i].off, c->macs[i].size, c->macs[i].mac); if (!encrypt_aes128_ctr(buf, buf, c->size, t->file_key, iv)) { err = g_error_new(MEGA_ERROR, MEGA_ERROR_OTHER, "Failed to encrypt data"); goto err; } // prepare URL including chunk offset chksum = upload_checksum(buf, c->size); url = g_strdup_printf("%s/%" G_GOFFSET_FORMAT "?c=%s", t->upload_url, c->offset, chksum); // perform upload POST http_set_content_type(h, "application/octet-stream"); http_set_progress_callback(h, tman_transfer_progress, c); http_set_speed(h, t->max_ul, t->max_dl); http_set_proxy(h, t->proxy); response = http_post(h, url, buf, c->size, &local_err); if (!response) { g_propagate_prefixed_error(&err, local_err, "Chunk upload failed: "); goto err; } gchar *upload_handle = NULL; if (response->len > 0) { // check for numeric error code if (response->len < 10 && g_regex_match_simple("^-(\\d+)$", response->str, 0, 0)) { int code = atoi(response->str); const char* err_str = "???"; switch (code) { case -3: err_str = "EAGAIN"; break; case -4: err_str = "EFAILED"; break; case -5: err_str = "ENOTFOUND"; break; case -6: err_str = "ETOOMANY"; break; case -7: err_str = "ERANGE"; break; case -8: err_str = "EEXPIRED"; break; case -14: err_str = "EKEY"; break; } err = g_error_new(MEGA_ERROR, MEGA_ERROR_OTHER, "Server returned error code %d (%s)", code, err_str); goto err; } if (response->len == 36) { upload_handle = base64urlencode(response->str, response->len); // we've got the handle tman_debug("W[%d]: got upload data handle with chunk %d: '%s'\n", worker->index, c->index, upload_handle); } else { err = g_error_new(MEGA_ERROR, MEGA_ERROR_OTHER, "Server returned something that does not look like upload handle"); goto err; } } tman_debug("W[%d]: success for chunk %d\n", worker->index, c->index); // final progress update for 100% msg = g_new0(struct transfer_manager_msg, 1); msg->type = TRANSFER_MANAGER_MSG_CHUNK_PROGRESS; msg->chunk = c; msg->transfered_size = c->size; g_async_queue_push(tman.manager_mailbox, msg); // send success to the manager msg = g_new0(struct transfer_manager_msg, 1); msg->type = TRANSFER_MANAGER_MSG_CHUNK_DONE; msg->chunk = c; msg->worker = worker; msg->upload_handle = upload_handle; g_async_queue_push(tman.manager_mailbox, msg); return; err: tman_debug("W[%d]: error for chunk %d: %s\n", worker->index, c->index, err->message); // final progress update for 0% (because we failed to transfer the chunk) msg = g_new0(struct transfer_manager_msg, 1); msg->type = TRANSFER_MANAGER_MSG_CHUNK_PROGRESS; msg->chunk = c; msg->transfered_size = 0; g_async_queue_push(tman.manager_mailbox, msg); // send error to the manager msg = g_new0(struct transfer_manager_msg, 1); msg->type = TRANSFER_MANAGER_MSG_CHUNK_FAILED; msg->chunk = c; msg->worker = worker; msg->error = err; g_async_queue_push(tman.manager_mailbox, msg); } // performs single http send operation static gpointer tman_worker_thread_fn(gpointer data) { struct transfer_worker* w = data; gc_http_free struct http *h = NULL; h = http_new(); http_expect_short_running(h); http_set_max_connects(h, 2); while (TRUE) { struct transfer_worker_msg *msg = g_async_queue_timeout_pop(w->mailbox, 1000); if (!msg) { continue; } switch (msg->type) { case TRANSFER_WORKER_MSG_STOP: g_free(msg); return NULL; case TRANSFER_WORKER_MSG_UPLOAD_CHUNK: tman_worker_upload_chunk(msg->chunk, w, h); g_free(msg); break; } } return NULL; } static void schedule_chunk_transfers(void) { GList *ti, *ti_next; GSList *ci; struct transfer_msg *tmsg; gint64 now = g_get_monotonic_time(); // first submit chunks to workers for (ti = tman.transfers; ti; ti = ti->next) { struct transfer *t = ti->data; // skip errored out transfers if (t->error) continue; for (ci = t->chunks; ci; ci = ci->next) { struct transfer_chunk *c = ci->data; // if the chunk is scheduled to be started in the // future, skip it if (c->start_at > now) continue; // stop if we can't start more chunk transfers if (tman.current_workers >= tman.max_workers) goto check_completed; // start chunk transfer if (c->status == CHUNK_STATE_QUEUED) { tman_debug("M: chunk %d pushed to worker\n", c->index); for (int i = 0; i < tman.max_workers; i++) { if (!tman.workers[i].busy) { tman.workers[i].busy = TRUE; c->status = CHUNK_STATE_IN_PROGRESS; tman.current_workers++; struct transfer_worker_msg *msg = g_new0(struct transfer_worker_msg, 1); msg->type = TRANSFER_WORKER_MSG_UPLOAD_CHUNK; msg->chunk = c; g_async_queue_push(tman.workers[i].mailbox, msg); goto queued; } } goto check_completed; queued:; } } } check_completed: // next check for finished transfers for (ti = tman.transfers; ti; ti = ti_next) { struct transfer *t = ti->data; ti_next = ti->next; // if some chunks are in flight or queued and transfer is not // aborted, we are not done yet gboolean is_finished = TRUE; for (ci = t->chunks; ci; ci = ci->next) { struct transfer_chunk *c = ci->data; if (c->status == CHUNK_STATE_IN_PROGRESS || (c->status == CHUNK_STATE_QUEUED && !t->error)) { is_finished = FALSE; break; } } if (!is_finished) continue; if (!t->error && !t->upload_handle) { // mega did not return upload handle with the last // uploaded chunk, WTF? t->error = g_error_new(MEGA_ERROR, MEGA_ERROR_NO_HANDLE, "Mega didn't return an upload handle"); } // remove transfer from the list tman.transfers = g_list_delete_link(tman.transfers, ti); tmsg = g_new0(struct transfer_msg, 1); if (t->error) { tman_debug("M: transfer %s failed\n", t->upload_url); tmsg->type = TRANSFER_MSG_ERROR; tmsg->error = t->error; t->error = NULL; } else { tman_debug("M: transfer %s succeeded\n", t->upload_url); tmsg->type = TRANSFER_MSG_DONE; // calculate meta_mac GSList *macs = NULL; for (ci = t->chunks; ci; ci = ci->next) { struct transfer_chunk *c = ci->data; for (int i = 0; i < c->n_macs; i++) macs = g_slist_prepend(macs, c->macs[i].mac); } macs = g_slist_reverse(macs); meta_mac_calculate(macs, t->file_key, tmsg->meta_mac); g_slist_free(macs); tmsg->upload_handle = t->upload_handle; } g_slist_free_full(t->chunks, g_free); t->chunks = NULL; g_async_queue_push(t->submitter_mailbox, tmsg); } } static int get_n_chunks(guint idx, guint size, guint* rounded_size) { if (idx < 7) { // special algo guint i = 0; guint csize = 0; while (csize < size) { csize += get_chunk_size(idx); idx++; i++; } *rounded_size = csize; return i; } else { guint rem = size % (1024 * 1024); int n = size / (1024 * 1024) + (rem > 0 ? 1 : 0); *rounded_size = n * 1024 * 1024; return n; } } static void prepare_transfer(struct transfer *t) { // create list of chunks and initialize it // get number of chunks from total size gint64 now = g_get_monotonic_time(); goffset off = 0; guint chunk_idx = 0, mac_idx = 0; // Here we create a list of chunks that will be used for transfer. // For bigger files, we can create bigger chunks. When creating // bigger chunks we need to account for a fact that chunk mac is // calculated from a fixed pattern of chunk sizes that we can't modify. // // First 8 chunks are 128 kiB * index sized, the rest is 1 MiB sized. // // We pre-define this pattern in the macs array of n_macs size. //XXX: mega largely works, but craps out EFAILs on too many connections when coalescing chunks so // let's disable boost for now. gboolean boost = FALSE; if (boost) { while (off < t->total_size) { goffset max_size = 16 * 1024 * 1024; if (chunk_idx < 3) max_size = 4 * 1024 * 1024; guint size = MIN(t->total_size - off, max_size); guint rounded_size; int num_mac_chunks = get_n_chunks(mac_idx, size, &rounded_size); size = MIN(t->total_size - off, rounded_size); struct transfer_chunk *c = g_malloc0(sizeof(struct transfer_chunk) + sizeof(struct transfer_chunk_mac) * num_mac_chunks); c->offset = off; c->size = size; c->index = chunk_idx; c->status = CHUNK_STATE_QUEUED; c->transfer = t; c->start_at = now + (chunk_idx < 4 ? chunk_idx : 4) * 200000; c->n_macs = num_mac_chunks; guint mac_off = 0; for (int i = 0; i < num_mac_chunks; i++) { c->macs[i].off = mac_off; c->macs[i].size = MIN(get_chunk_size(mac_idx), size - mac_off); mac_idx++; mac_off += c->macs[i].size; } t->chunks = g_slist_prepend(t->chunks, c); chunk_idx++; off += c->size; // curiously, I found that if the last chunk // is not coalesced, mega will not hang if (off == t->total_size && c->n_macs > 1) { off -= c->macs[c->n_macs - 1].size; c->size -= c->macs[c->n_macs - 1].size; c->n_macs--; } } } else { for (chunk_idx = 0; off < t->total_size; chunk_idx++) { struct transfer_chunk *c = g_malloc0(sizeof(struct transfer_chunk) + sizeof(struct transfer_chunk_mac) * 1); c->offset = off; c->size = MIN(t->total_size - off, get_chunk_size(chunk_idx)); c->index = chunk_idx; c->status = CHUNK_STATE_QUEUED; c->transfer = t; c->start_at = now + (chunk_idx < 4 ? chunk_idx : 4) * 200000; c->n_macs = 1; c->macs[0].off = 0; c->macs[0].size = c->size; t->chunks = g_slist_prepend(t->chunks, c); off += c->size; } } t->chunks = g_slist_reverse(t->chunks); #if 0 GSList* it; for (it = t->chunks; it; it = it->next) { struct transfer_chunk *c = it->data; g_print("CH[%d] = { off=%" G_GOFFSET_FORMAT " size=%" G_GOFFSET_FORMAT " macs=%d }\n", c->index, c->offset, c->size, c->n_macs); for (int i = 0; i < c->n_macs; i++) g_print(" CMAC { off=%u size=%u }\n", c->macs[i].off, c->macs[i].size); } #endif } static gpointer tman_manager_thread_fn(gpointer data) { // keeps track of the workers // - submits transfer_chunks to the workers // keeps tab on the individual trasnfers and // - notifies transfer submitter when done // - sends progress updates while (TRUE) { struct transfer_manager_msg *msg = g_async_queue_timeout_pop(tman.manager_mailbox, 100); struct transfer_msg *tmsg; struct transfer *t; struct transfer_chunk *c; if (!msg) { schedule_chunk_transfers(); continue; } switch (msg->type) { case TRANSFER_MANAGER_MSG_STOP: g_free(msg); return NULL; case TRANSFER_MANAGER_MSG_SUBMIT_TRANSFER: t = msg->transfer; tman_debug("M: transfer submitted %s\n", t->upload_url); prepare_transfer(t); tman.transfers = g_list_append(tman.transfers, t); schedule_chunk_transfers(); break; case TRANSFER_MANAGER_MSG_CHUNK_PROGRESS: c = msg->chunk; t = c->transfer; //tman_debug("M: chunk progress for %d\n", c->index); tmsg = g_new0(struct transfer_msg, 1); tmsg->type = TRANSFER_MSG_PROGRESS; // update overall progress t->transfered_size += msg->transfered_size - c->transfered_size; c->transfered_size = msg->transfered_size; tmsg->total_size = t->total_size; tmsg->transfered_size = t->transfered_size; g_async_queue_push(t->submitter_mailbox, tmsg); break; case TRANSFER_MANAGER_MSG_CHUNK_DONE: c = msg->chunk; t = c->transfer; tman_debug("M: chunk done %d\n", c->index); msg->worker->busy = FALSE; tman.current_workers--; c->status = CHUNK_STATE_DONE; if (msg->upload_handle) { g_free(t->upload_handle); t->upload_handle = msg->upload_handle; } schedule_chunk_transfers(); break; case TRANSFER_MANAGER_MSG_CHUNK_FAILED: { c = msg->chunk; t = c->transfer; tman_debug("M: chunk fail %d\n", c->index); msg->worker->busy = FALSE; tman.current_workers--; // re-queue chunk c->status = CHUNK_STATE_QUEUED; gint64 now = g_get_monotonic_time(); if (t->error) { // transfer is in error state and is being aborted tman_debug("M: transfer is being aborted, chunk %d fail ignored\n", c->index); } else { if (g_error_matches(msg->error, HTTP_ERROR, HTTP_ERROR_COMM_FAILURE) || g_error_matches(msg->error, HTTP_ERROR, HTTP_ERROR_TIMEOUT) || g_error_matches(msg->error, HTTP_ERROR, HTTP_ERROR_SERVER_BUSY) || g_error_matches(msg->error, HTTP_ERROR, HTTP_ERROR_NO_RESPONSE)) { if (c->failures_count > 8) { g_printerr("WARNING: chunk upload failed too many times (%s), aborting transfer\n", msg->error->message); // mark transfer as aborted t->error = msg->error; msg->error = NULL; } else { g_printerr("WARNING: chunk upload failed (%s), re-trying after %d seconds\n", msg->error->message, (1 << c->failures_count)); c->start_at = g_get_monotonic_time() + 1000 * 1000 * ((1 << c->failures_count)); c->failures_count++; } } else if (g_error_matches(msg->error, HTTP_ERROR, HTTP_ERROR_BANDWIDTH_LIMIT)) { // we need to defer all further // transfers by a lot g_printerr("WARNING: over upload quota, delaying all transfers by 5 minutes\n"); GSList* ci; for (ci = t->chunks; ci; ci = ci->next) { struct transfer_chunk *c = ci->data; c->start_at = now + 1000 * (5*60*1000 + g_random_int_range(0, 2000)); } } else { g_printerr("WARNING: chunk upload failed (%s), aborting transfer\n", msg->error->message); // mark transfer as aborted t->error = msg->error; msg->error = NULL; } } schedule_chunk_transfers(); break; } default: g_assert_not_reached(); break; } g_clear_error(&msg->error); g_free(msg); } return NULL; } static void tman_init(int max_workers) { GError *local_err = NULL; // check if we are already initialized if (tman.manager_thread) return; memset(&tman, 0, sizeof tman); tman.manager_mailbox = g_async_queue_new(); // start workers tman.max_workers = max_workers; tman.workers = g_new0(struct transfer_worker, max_workers); for (int i = 0; i < max_workers; i++) { tman.workers[i].index = i; tman.workers[i].mailbox = g_async_queue_new(); tman.workers[i].thread = g_thread_new("transfer worker", tman_worker_thread_fn, &tman.workers[i]); } // start manager tman.manager_thread = g_thread_new("transfer manager", tman_manager_thread_fn, &tman); } static void tman_fini(void) { if (tman.manager_thread) { // ask manager to stop struct transfer_manager_msg *msg = g_new0(struct transfer_manager_msg, 1); msg->type = TRANSFER_MANAGER_MSG_STOP; g_async_queue_push(tman.manager_mailbox, msg); g_thread_join(tman.manager_thread); for (int i = 0; i < tman.max_workers; i++) { struct transfer_worker_msg *msg = g_new0(struct transfer_worker_msg, 1); msg->type = TRANSFER_WORKER_MSG_STOP; g_async_queue_push(tman.workers[i].mailbox, msg); } for (int i = 0; i < tman.max_workers; i++) { g_thread_join(tman.workers[i].thread); g_async_queue_unref(tman.workers[i].mailbox); } g_free(tman.workers); g_async_queue_unref(tman.manager_mailbox); memset(&tman, 0, sizeof tman); } } static gboolean tman_run_upload_transfer( // in: struct mega_session *s, guchar file_key[16], guchar nonce[8], const gchar *upload_url, GFileInputStream *istream, goffset file_size, // out: gchar **upload_handle, guchar meta_mac[16], GError **err) { struct transfer t = { 0 }; gboolean retval = FALSE; struct mega_status_data status_data; g_return_val_if_fail(file_key != NULL, FALSE); g_return_val_if_fail(nonce != NULL, FALSE); g_return_val_if_fail(upload_url != NULL, FALSE); g_return_val_if_fail(istream != NULL, FALSE); g_return_val_if_fail(upload_handle != NULL, FALSE); g_return_val_if_fail(meta_mac != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); // initialize transfer data t.submitter_mailbox = g_async_queue_new(); t.total_size = file_size; AES_set_encrypt_key(file_key, 128, &t.k); memcpy(t.file_key, file_key, 16); memcpy(t.nonce, nonce, 8); g_mutex_init(&t.stream_lock); t.istream = istream; t.upload_url = upload_url; t.max_ul = s->max_ul; t.max_dl = s->max_dl; t.proxy = s->proxy; // tell the manager to start the transfer struct transfer_manager_msg *msg = g_new0(struct transfer_manager_msg, 1); msg->type = TRANSFER_MANAGER_MSG_SUBMIT_TRANSFER; msg->transfer = &t; g_async_queue_push(tman.manager_mailbox, msg); // send initial progress report status_data = (struct mega_status_data){ .type = MEGA_STATUS_PROGRESS, .progress.total = file_size, .progress.done = -1, }; send_status(s, &status_data); // wait until the transfer finishes while (TRUE) { struct transfer_msg *msg = g_async_queue_pop(t.submitter_mailbox); switch (msg->type) { case TRANSFER_MSG_DONE: *upload_handle = msg->upload_handle; memcpy(meta_mac, msg->meta_mac, 16); g_free(msg); retval = TRUE; goto out; case TRANSFER_MSG_ERROR: g_propagate_prefixed_error(err, msg->error, "Upload transfer failed: "); g_free(msg); goto out; case TRANSFER_MSG_PROGRESS: status_data = (struct mega_status_data){ .type = MEGA_STATUS_PROGRESS, .progress.total = msg->total_size, .progress.done = msg->transfered_size, }; send_status(s, &status_data); break; default: g_assert_not_reached(); } g_free(msg); } out: // send final progress report status_data = (struct mega_status_data){ .type = MEGA_STATUS_PROGRESS, .progress.total = file_size, .progress.done = -2, }; send_status(s, &status_data); // cleanup g_async_queue_unref(t.submitter_mailbox); g_mutex_clear(&t.stream_lock); return retval; } // }}} // Download/upload: // {{{ create_preview static gint has_convert = -1; static gint has_ffmpegthumbnailer = -1; static gchar *create_preview(struct mega_session *s, const gchar *local_path, const guchar *key, GError **err) { gchar *handle = NULL; #ifndef G_OS_WIN32 GError *local_err = NULL; gc_free gchar *tmp1 = NULL, *tmp2 = NULL; if (has_ffmpegthumbnailer < 0) { gc_free gchar *prg = g_find_program_in_path("ffmpegthumbnailer"); has_ffmpegthumbnailer = prg ? 1 : 0; } if (has_convert < 0) { gc_free gchar *prg = g_find_program_in_path("convert"); has_convert = prg ? 1 : 0; } if (has_ffmpegthumbnailer && g_regex_match_simple("\\.(mpg|mpeg|avi|mkv|flv|rm|mp4|wmv|asf|ram|mov)$", local_path, G_REGEX_CASELESS, 0)) { gchar buf[50] = "/tmp/megatools.XXXXXX"; gchar *dir = g_mkdtemp(buf); if (dir) { gint status = 1; gc_free gchar *thumb_path = g_strdup_printf("%s/thumb.jpg", dir); gc_free gchar *qpath = g_shell_quote(local_path); gc_free gchar *tmp = g_strdup_printf( "ffmpegthumbnailer -t 5 -i %s -o %s/thumb.jpg -s 128 -f -a", qpath, dir); if (g_spawn_command_line_sync(tmp, &tmp1, &tmp2, &status, &local_err)) { if (g_file_test(thumb_path, G_FILE_TEST_IS_REGULAR)) { gc_free gchar *thumb_data = NULL; gsize thumb_len; if (g_file_get_contents(thumb_path, &thumb_data, &thumb_len, NULL)) { handle = mega_session_new_node_attribute(s, thumb_data, thumb_len, "0", key, &local_err); if (!handle) g_propagate_error(err, local_err); } g_unlink(thumb_path); } } else { g_propagate_error(err, local_err); } g_rmdir(dir); } } else if (has_convert && g_regex_match_simple("\\.(jpe?g|png|gif|bmp|tiff|svg|pnm|eps|ico|pdf)$", local_path, G_REGEX_CASELESS, 0)) { gchar buf[50] = "/tmp/megatools.XXXXXX"; gchar *dir = g_mkdtemp(buf); if (dir) { gint status = 1; gc_free gchar *thumb_path = g_strdup_printf("%s/thumb.jpg", dir); gc_free gchar *qpath = NULL; if (g_regex_match_simple("\\.pdf$", local_path, G_REGEX_CASELESS, 0)) { gc_free gchar *local_path_page = g_strdup_printf("%s[0]", local_path); qpath = g_shell_quote(local_path_page); } else { qpath = g_shell_quote(local_path); } gc_free gchar *tmp = g_strdup_printf( "convert %s -strip -background white -flatten -resize 128x128^ -gravity center -crop 128x128+0+0 +repage %s/thumb.jpg", qpath, dir); if (g_spawn_command_line_sync(tmp, &tmp1, &tmp2, &status, &local_err)) { if (g_file_test(thumb_path, G_FILE_TEST_IS_REGULAR)) { gc_free gchar *thumb_data = NULL; gsize thumb_len; if (g_file_get_contents(thumb_path, &thumb_data, &thumb_len, NULL)) { handle = mega_session_new_node_attribute(s, thumb_data, thumb_len, "0", key, &local_err); if (!handle) g_propagate_error(err, local_err); } g_unlink(thumb_path); } } else { g_propagate_error(err, local_err); } g_rmdir(dir); } } else { return NULL; } if (!handle && err && !*err) g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't create preview"); #endif return handle; } // }}} // {{{ mega_session_put struct mega_node *mega_session_put(struct mega_session *s, struct mega_node *parent_node, const gchar* remote_name, GFileInputStream *stream, const gchar* local_path, GError **err) { GError *local_err = NULL; gc_free gchar *up_handle = NULL; gc_free gchar *up_node = NULL; gc_free gchar *p_url = NULL; g_return_val_if_fail(s != NULL, NULL); g_return_val_if_fail(parent_node != NULL, NULL); g_return_val_if_fail(remote_name != NULL, NULL); g_return_val_if_fail(stream != NULL, NULL); g_return_val_if_fail(err == NULL || *err == NULL, NULL); gc_object_unref GFileInfo *info = g_file_input_stream_query_info(stream, G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, &local_err); if (!info) { g_propagate_prefixed_error(err, local_err, "Can't get stream info: "); return NULL; } goffset file_size = g_file_info_get_size(info); // setup encryption gc_free guchar *aes_key = make_random_key(); gc_free guchar *nonce = make_random_key(); guchar meta_mac[16]; int retries = 3; gboolean transfer_ok; tman_init(s->max_workers); try_again: // ask for upload url - [{"a":"u","ssl":0,"ms":0,"s":,"r":0,"e":0}] g_free(up_node); up_node = api_call(s, 'o', NULL, &local_err, "[{a:u, e:0, ms:0, r:0, s:%i, ssl:0, v:2}]", (gint64)file_size); if (!up_node) { g_propagate_error(err, local_err); return NULL; } g_free(p_url); p_url = s_json_get_member_string(up_node, "p"); transfer_ok = tman_run_upload_transfer( /* in: */ s, aes_key, nonce, p_url, stream, file_size, /* out: */ &up_handle, meta_mac, &local_err); if (!transfer_ok) { if (retries-- > 0) { g_printerr("WARNING: Mega upload failed (%s), retrying transfer (%d retries left)\n", local_err ? local_err->message : "???", retries); g_clear_error(&local_err); goto try_again; } g_propagate_prefixed_error(err, local_err, "Data upload failed: "); return NULL; } // create preview gc_free gchar *fa = NULL; if (s->create_preview) fa = create_preview(s, local_path, aes_key, NULL); gc_free gchar *attrs = encode_node_attrs(remote_name); gc_free gchar *attrs_enc = b64_aes128_cbc_encrypt_str(attrs, aes_key); guchar node_key[32]; pack_node_key(node_key, aes_key, nonce, meta_mac); gc_free gchar *node_key_enc = b64_aes128_encrypt(node_key, 32, s->master_key); // prepare request gc_free gchar *put_node = api_call(s, 'o', NULL, &local_err, "[{a:p, t:%s, n:[{h:%s, t:0, k:%s, a:%s, fa:%s}]}]", parent_node->handle, up_handle, node_key_enc, attrs_enc, fa); if (!put_node) { g_propagate_error(err, local_err); return NULL; } const gchar *f_arr = s_json_get_member(put_node, "f"); if (s_json_get_type(f_arr) != S_JSON_TYPE_ARRAY) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid response"); return NULL; } const gchar *f_el = s_json_get_element(f_arr, 0); if (!f_el || s_json_get_type(f_el) != S_JSON_TYPE_OBJECT) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid response"); return NULL; } struct mega_node *nn = mega_node_parse(s, f_el); if (!nn) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid response"); return NULL; } // add uploaded node to the filesystem s->fs_nodes = g_slist_append(s->fs_nodes, nn); nn->parent = parent_node; return nn; } // }}} // {{{ mega_session_download_data struct get_data_state { struct mega_session *s; GOutputStream *ostream; EVP_CIPHER_CTX *ctx; struct chunked_cbc_mac mac; struct chunked_cbc_mac mac_saved; guint64 progress_offset; guint64 progress_total; }; static gboolean progress_dl(goffset dltotal, goffset dlnow, goffset ultotal, goffset ulnow, gpointer user_data) { struct get_data_state *data = user_data; struct mega_status_data status_data = { .type = MEGA_STATUS_PROGRESS, .progress.total = data->progress_total, .progress.done = data->progress_offset + dlnow, }; send_status(data->s, &status_data); return TRUE; } static gsize get_data_cb(gpointer buffer, gsize size, gpointer user_data) { struct get_data_state *data = user_data; gc_error_free GError *local_err = NULL; int out_len; if (!EVP_EncryptUpdate(data->ctx, buffer, &out_len, buffer, size)) { g_printerr("ERROR: Failed to decrypt data during download\n"); return 0; } if (out_len != size) { g_printerr("ERROR: Failed to decrypt data during download (out_len != size)\n"); return 0; } if (!chunked_cbc_mac_update(&data->mac, buffer, size)) { g_printerr("ERROR: Failed to run mac calculator during download\n"); return 0; } struct mega_status_data status_data = { .type = MEGA_STATUS_DATA, .data.size = size, .data.buf = buffer, }; send_status(data->s, &status_data); if (!data->ostream) return size; if (!g_output_stream_write_all(data->ostream, buffer, size, NULL, NULL, &local_err)) { g_printerr("ERROR: Failed writing to stream: %s\n", local_err->message); return 0; } return size; } static gboolean evp_set_ctr_postion(EVP_CIPHER_CTX* ctx, guint64 off, guchar* nonce, guchar* key, GError** err) { g_return_val_if_fail(ctx != NULL, FALSE); g_return_val_if_fail(nonce != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); union { guchar iv[16]; struct { guchar nonce[8]; guint64 ctr; }; } iv; _Static_assert(sizeof(iv) == 16, "iv union is not 16bytes long, you're using an interesting architecture"); memcpy(iv.nonce, nonce, 8); iv.ctr = GUINT64_TO_BE(off / 16); if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv.iv)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Failed to init aes-ctr decryptor"); return FALSE; } EVP_CIPHER_CTX_set_padding(ctx, 0); // offset may be up to 15 bytes into a block, do some dummy decryption, // to put EVP into a correct state guchar scratch[16]; int out_len; int ib_off = off % 16; if (ib_off > 0) { if (!EVP_EncryptUpdate(ctx, scratch, &out_len, scratch, ib_off) || out_len != ib_off) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Failed to init aes-ctr decryptor (intra-block)"); return FALSE; } } return TRUE; } #define RESUME_BUF_SIZE (1024 * 1024) gboolean mega_session_download_data(struct mega_session *s, struct mega_download_data_params *params, GFile *file, GError **err) { GError *local_err = NULL; gc_object_unref GFile *dir = NULL; gc_object_unref GFile *tmp_file = NULL; gc_object_unref GFileIOStream *iostream = NULL; gc_object_unref GFileOutputStream *ostream = NULL; GSeekable* seekable = NULL; struct get_data_state state = { .s = s }; gc_free gchar *tmp_path = NULL, *file_path = NULL, *tmp_name = NULL; guint64 download_from = 0; gssize bytes_read; gc_free guchar *buf = NULL; struct mega_status_data status_data; g_return_val_if_fail(err == NULL || *err == NULL, FALSE); status_data = (struct mega_status_data) { .type = MEGA_STATUS_FILEINFO, .fileinfo.name = params->node_name, .fileinfo.size = params->node_size, }; send_status(s, &status_data); // initialize decrytpion key/state guchar aes_key[16], meta_mac_xor[8], nonce[8]; unpack_node_key(params->node_key, aes_key, nonce, meta_mac_xor); if (!chunked_cbc_mac_init8(&state.mac, aes_key, nonce)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Failed to init mac calculator"); return FALSE; } if (file) { file_path = g_file_get_path(file); dir = g_file_get_parent(file); if (!dir) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "File %s does not have a parent directory", file_path); return FALSE; } tmp_name = g_strdup_printf(".megatmp.%s", params->node_handle); tmp_file = g_file_get_child(dir, tmp_name); if (tmp_file == NULL) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't get temporary file %s", tmp_name); return FALSE; } tmp_path = g_file_get_path(tmp_file); if (s->resume_enabled) { // if temporary file exists, read it and initialize the // meta mac calculator state with the contents if (g_file_query_exists(tmp_file, NULL)) { iostream = g_file_open_readwrite(tmp_file, NULL, &local_err); if (iostream == NULL) { g_propagate_prefixed_error(err, local_err, "Can't open previous temporary file for resume %s", tmp_path); return FALSE; } buf = g_malloc(RESUME_BUF_SIZE); GInputStream* istream = g_io_stream_get_input_stream(G_IO_STREAM(iostream)); state.ostream = g_io_stream_get_output_stream(G_IO_STREAM(iostream)); seekable = G_SEEKABLE(iostream); while (TRUE) { bytes_read = g_input_stream_read(istream, buf, RESUME_BUF_SIZE, NULL, &local_err); if (bytes_read == 0) break; if (bytes_read < 0) { g_propagate_prefixed_error( err, local_err, "Can't read previous temporary file for resume %s", tmp_path); return FALSE; } // update cbc-mac if (!chunked_cbc_mac_update(&state.mac, buf, bytes_read)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Failed to run mac calculator during resume"); return FALSE; } download_from += bytes_read; } if (download_from > params->node_size) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Unfinished downloaded data is larger than the node being downloaded (you can eg. remove %s to fix the issue)", tmp_path); return FALSE; } } else { // create a new temporary file ostream = g_file_create(tmp_file, 0, NULL, &local_err); if (!ostream) { g_propagate_prefixed_error(err, local_err, "Can't open local file %s for writing: ", tmp_path); return FALSE; } seekable = G_SEEKABLE(ostream); state.ostream = G_OUTPUT_STREAM(ostream); } } else {// !resume_enabled // if temporary file exists, delete it if (g_file_query_exists(tmp_file, NULL) && !g_file_delete(tmp_file, NULL, &local_err)) { g_propagate_prefixed_error(err, local_err, "Can't delete previous temporary file %s", tmp_name); return FALSE; } // create temporary file ostream = g_file_create(tmp_file, 0, NULL, &local_err); if (!ostream) { g_propagate_prefixed_error(err, local_err, "Can't open local file %s for writing: ", tmp_path); return FALSE; } seekable = G_SEEKABLE(ostream); state.ostream = G_OUTPUT_STREAM(ostream); } } // init decryptor state.ctx = EVP_CIPHER_CTX_new(); if (!state.ctx) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Failed to init aes-ctr decryptor"); return FALSE; } if (!EVP_EncryptInit_ex(state.ctx, EVP_aes_128_ctr(), NULL, NULL, NULL)) { EVP_CIPHER_CTX_free(state.ctx); g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Failed to init aes-ctr decryptor"); return FALSE; } EVP_CIPHER_CTX_set_padding(state.ctx, 0); if (!evp_set_ctr_postion(state.ctx, download_from, nonce, aes_key, &local_err)) { EVP_CIPHER_CTX_free(state.ctx); g_propagate_error(err, local_err); return FALSE; } // send initial progress report status_data = (struct mega_status_data) { .type = MEGA_STATUS_PROGRESS, .progress.total = params->node_size - download_from, .progress.done = -1, }; send_status(s, &status_data); // We'll download the file sequentially in 256MB increments (chunks), // re-trying if a chunk fails. We will pre-encrypt and pre-calculate mac // for the chunk so that we don't need to do it again when download // fails. // // It's a big chunk, because mega takes ~800ms to respond to a POST // request for data. So if we use 16MB we'd naturally limit ourselves // to ~18MiB/s on an infinitely fast network. With big chunk size, // the limit is proportionally higher. // // Because meta-mac calculation can't be reversed in case of a chunk // download failure, we save its state prior to chunk download and // restore it from saved state in case we need to rewind. state.progress_total = params->node_size - download_from; state.progress_offset = 0; const guint64 chunk_size = 256 * 1024 * 1024; while (download_from < params->node_size) { guint64 from = download_from; guint64 to = download_from + MIN(params->node_size - download_from, chunk_size); state.mac_saved = state.mac; guint tries = 0; gboolean download_ok; struct http *h; gc_free gchar *url = g_strdup_printf("%s/%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT, params->download_url, from, to - 1); // 640 minutes should be enough for everyone const gint64 retry_timeout = 1000ll * 1000 * 60 * 640; gint64 end_time = g_get_monotonic_time() + retry_timeout; retry: // perform download h = http_new(); http_set_progress_callback(h, progress_dl, &state); http_set_speed(h, s->max_ul, s->max_dl); http_set_proxy(h, s->proxy); download_ok = http_post_stream_download(h, url, get_data_cb, &state, &local_err); http_free(h); if (!download_ok) { // retry timeout reached if (g_get_monotonic_time() > end_time) { g_propagate_prefixed_error(err, local_err, "Data download failed: "); goto err_noremove; } // we wait at most 256 seconds between retries (~4 minutes) tries = MIN(tries + 1, 8); // we only retry if we can seek the stream if (seekable) { g_printerr("WARNING: chunk download failed (%s), re-trying after %d seconds\n", local_err ? local_err->message : "?", (1 << tries)); g_clear_error(&local_err); } // restore saved mac calculation state state.mac = state.mac_saved; // seek back the stream if (!g_seekable_seek(seekable, from, G_SEEK_SET, NULL, &local_err)) { g_propagate_prefixed_error(err, local_err, "Failed to rewind the temporary file after chunk download failure"); goto err_noremove; } // restore CTR encryption state if (!evp_set_ctr_postion(state.ctx, from, nonce, aes_key, &local_err)) { g_propagate_prefixed_error(err, local_err, "Failed to rewind the temporary file after chunk download failure"); goto err_noremove; } g_usleep(1000 * 1000 * (1 << tries)); goto retry; } // move to the next chunk state.progress_offset += to - from; download_from = to; } status_data = (struct mega_status_data){ .type = MEGA_STATUS_PROGRESS, .progress.total = params->node_size - download_from, .progress.done = -2, }; send_status(s, &status_data); // check mac of the downloaded file guchar meta_mac_xor_calc[8]; if (!chunked_cbc_mac_finish8(&state.mac, meta_mac_xor_calc)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Failed to finish mac calculator"); goto err; } if (memcmp(meta_mac_xor, meta_mac_xor_calc, 8) != 0) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "MAC mismatch"); goto err; } if (ostream && !g_output_stream_close(G_OUTPUT_STREAM(ostream), NULL, &local_err)) { g_propagate_prefixed_error(err, local_err, "Can't close downloaded file: "); goto err_noremove; } if (iostream && !g_io_stream_close(G_IO_STREAM(iostream), NULL, &local_err)) { g_propagate_prefixed_error(err, local_err, "Can't close downloaded file: "); goto err_noremove; } EVP_CIPHER_CTX_free(state.ctx); if (file && !g_file_move(tmp_file, file, G_FILE_COPY_NOFOLLOW_SYMLINKS | G_FILE_COPY_NO_FALLBACK_FOR_MOVE, NULL, NULL, NULL, &local_err)) { g_propagate_prefixed_error( err, local_err, "Can't rename downloaded temporary file %s to %s (downloaded data is good!): ", tmp_path, file_path); return FALSE; } return TRUE; err: if (tmp_file) g_file_delete(tmp_file, NULL, NULL); err_noremove: EVP_CIPHER_CTX_free(state.ctx); return FALSE; } void mega_download_data_free(struct mega_download_data_params *params) { g_clear_pointer(¶ms->download_url, g_free); g_clear_pointer(¶ms->node_handle, g_free); g_clear_pointer(¶ms->node_name, g_free); } // }}} // {{{ mega_session_get gboolean mega_session_get(struct mega_session *s, GFile *file, struct mega_node *node, GError **err) { GError *local_err = NULL; gc_free gchar *get_node = NULL, *url = NULL; struct mega_download_data_params p = {}; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(node != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); // prepare request get_node = api_call(s, 'o', NULL, &local_err, "[{a:g, g:1, ssl:0, n:%s}]", node->handle); if (!get_node) { g_propagate_error(err, local_err); return FALSE; } gint64 node_size = s_json_get_member_int(get_node, "s", -1); if (node_size < 0) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't determine file size"); return FALSE; } url = s_json_get_member_string(get_node, "g"); if (!url) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't determine download url"); return FALSE; } // sanity, caller should never pass non-file node g_assert(node->key_len == sizeof p.node_key); memcpy(p.node_key, node->key, node->key_len); p.download_url = url; p.node_handle = node->handle; p.node_name = node->name; p.node_size = node_size; return mega_session_download_data(s, &p, file, err); } // }}} // {{{ mega_session_dl_prepare gboolean mega_session_dl_prepare(struct mega_session *s, struct mega_download_data_params *params, const gchar *handle, const gchar *key, GError **err) { GError *local_err = NULL; gc_free gchar *node_name = NULL, *dl_node = NULL, *url = NULL, *at = NULL; gc_free guchar *node_key = NULL; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(handle != NULL, FALSE); g_return_val_if_fail(key != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); // prepare request dl_node = api_call(s, 'o', NULL, &local_err, "[{a:g, g:1, ssl:0, p:%s}]", handle); if (!dl_node) { g_propagate_error(err, local_err); return FALSE; } // get file size gint64 node_size = s_json_get_member_int(dl_node, "s", -1); if (node_size < 0) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't determine file size"); return FALSE; } url = s_json_get_member_string(dl_node, "g"); if (!url) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't determine download url"); return FALSE; } at = s_json_get_member_string(dl_node, "at"); if (!at) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't get file attributes"); return FALSE; } // decode node_key gsize node_key_len = 0; node_key = base64urldecode(key, &node_key_len); if (!node_key || node_key_len != 32) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't retrieve file key"); return FALSE; } // initialize decrytpion key guchar aes_key[16]; unpack_node_key(node_key, aes_key, NULL, NULL); // decrypt attributes with aes_key if (!decrypt_node_attrs(at, aes_key, &node_name)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid key"); return FALSE; } if (!node_name) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't retrieve remote file name"); return FALSE; } // replace invalid filename characters with whitespace gchar *check = node_name; #ifdef G_OS_WIN32 while ((check = strpbrk(check, "/\\<>:\"|?*"))) #else while ((check = strpbrk(check, "/"))) #endif *check = '_'; // check for invalid names if (!strcmp(node_name, ".") || !strcmp(node_name, "..")) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Remote file name is invalid: '%s'", node_name); return FALSE; } memcpy(params->node_key, node_key, sizeof params->node_key); params->download_url = url; params->node_handle = g_strdup(handle); params->node_name = node_name; params->node_size = node_size; url = node_name = NULL; return TRUE; } // }}} // {{{ mega_session_get_node_by_handle struct mega_node* mega_session_get_node_by_handle(struct mega_session *s, const gchar* handle) { GSList *i; g_return_val_if_fail(s != NULL, NULL); for (i = s->fs_nodes; i; i = i->next) { struct mega_node *n = i->data; if (n->handle && g_str_equal(n->handle, handle)) return n; } return NULL; } // }}} // {{{ mega_node_get_link gchar *mega_node_get_link(struct mega_node *n, gboolean include_key) { g_return_val_if_fail(n != NULL, NULL); if (n->link) { if (include_key && n->key) { gc_free gchar *key = mega_node_get_key(n); return g_strdup_printf("https://mega.nz/#!%s!%s", n->link, key); } return g_strdup_printf("https://mega.nz/#!%s", n->link); } return NULL; } // }}} // {{{ mega_node_get_key gchar *mega_node_get_key(struct mega_node *n) { g_return_val_if_fail(n != NULL, NULL); if (n->key) return base64urlencode(n->key, n->key_len); return NULL; } // }}} // {{{ mega_node_get_path gchar *mega_node_get_path_dup(struct mega_node *n) { gchar path[4096]; if (mega_node_get_path(n, path, sizeof(path))) return g_strndup(path, sizeof(path)); return NULL; } gboolean mega_node_get_path(struct mega_node *n, gchar *buf, gsize len) { g_return_val_if_fail(n != NULL, FALSE); // count parents gint n_parents = 0; struct mega_node *it = n; while (it && n_parents < 64) { n_parents++; it = it->parent; } if (n_parents >= 64) return FALSE; // allocate pointer list on stack struct mega_node *parents[64]; // get parents into the pointer list it = n; n_parents = 0; while (it) { parents[n_parents++] = it; it = it->parent; } // reverse iteration gint i; gchar *p = buf; for (i = n_parents - 1; i >= 0; i--) { it = parents[i]; gint name_len = strlen(it->name); gint remaining_buf_len = len - (p - buf); if (remaining_buf_len < name_len + 2) return FALSE; *p = '/'; p += 1; memcpy(p, it->name, name_len); p += name_len; } *p = '\0'; return TRUE; } // }}} // {{{ mega_node_is_container gboolean mega_node_is_container(struct mega_node *n) { return n && n->type != MEGA_NODE_FILE; } // }}} // {{{ mega_node_has_ancestor gboolean mega_node_has_ancestor(struct mega_node *n, struct mega_node *ancestor) { g_return_val_if_fail(n != NULL, FALSE); g_return_val_if_fail(ancestor != NULL, FALSE); struct mega_node* it = n->parent; while (it) { if (it == ancestor) return TRUE; it = it->parent; } return FALSE; } // }}} // {{{ mega_session_save static void save_share_keys(gchar *handle, gchar *key, SJsonGen *gen) { s_json_gen_start_object(gen); s_json_gen_member_string(gen, "handle", handle); s_json_gen_member_bytes(gen, "key", key, 16); s_json_gen_end_object(gen); } gboolean mega_session_save(struct mega_session *s, GError **err) { GError *local_err = NULL; GSList *i; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(s->user_email != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); // calculate cache file path gc_free gchar *un = g_ascii_strdown(s->user_email, -1); gc_checksum_free GChecksum *cs = g_checksum_new(G_CHECKSUM_SHA1); g_checksum_update(cs, un, -1); gc_free gchar *filename = g_strconcat(g_checksum_get_string(cs), ".megatools.cache", NULL); gc_free gchar *path = g_build_filename(g_get_tmp_dir(), filename, NULL); SJsonGen *gen = s_json_gen_new(); s_json_gen_start_object(gen); // serialize session object s_json_gen_member_int(gen, "version", CACHE_FORMAT_VERSION); s_json_gen_member_int(gen, "last_refresh", s->last_refresh); s_json_gen_member_string(gen, "sid", s->sid); s_json_gen_member_string(gen, "password_salt_v2", s->password_salt_v2); s_json_gen_member_bytes(gen, "password_key", s->password_key, 16); s_json_gen_member_bytes(gen, "master_key", s->master_key, 16); s_json_gen_member_rsa_key(gen, "rsa_key", &s->rsa_key); s_json_gen_member_string(gen, "user_handle", s->user_handle); s_json_gen_member_string(gen, "user_name", s->user_name); s_json_gen_member_string(gen, "user_email", s->user_email); s_json_gen_member_array(gen, "share_keys"); g_hash_table_foreach(s->share_keys, (GHFunc)save_share_keys, gen); s_json_gen_end_array(gen); s_json_gen_member_array(gen, "fs_nodes"); for (i = s->fs_nodes; i; i = i->next) { struct mega_node *n = i->data; s_json_gen_start_object(gen); s_json_gen_member_string(gen, "name", n->name); s_json_gen_member_string(gen, "handle", n->handle); s_json_gen_member_string(gen, "parent_handle", n->parent_handle); s_json_gen_member_string(gen, "user_handle", n->user_handle); s_json_gen_member_string(gen, "su_handle", n->su_handle); s_json_gen_member_bytes(gen, "key", n->key, n->key_len); s_json_gen_member_int(gen, "type", n->type); s_json_gen_member_int(gen, "size", n->size); s_json_gen_member_int(gen, "timestamp", n->timestamp); s_json_gen_member_string(gen, "link", n->link); s_json_gen_end_object(gen); } s_json_gen_end_array(gen); s_json_gen_end_object(gen); gc_free gchar *cache_data = s_json_gen_done(gen); if (mega_debug & MEGA_DEBUG_CACHE) print_node(cache_data, "SAVE CACHE: "); gc_free gchar *tmp = g_strconcat("MEGA", cache_data, NULL); gc_free gchar *cipher = b64_aes128_cbc_encrypt_str(tmp, s->password_key_save); if (!g_file_set_contents(path, cipher, -1, &local_err)) { g_propagate_error(err, local_err); return FALSE; } return TRUE; } // }}} // {{{ mega_session_load static gboolean mega_session_load(struct mega_session *s, const gchar *un, const gchar *pw, gint max_age, gchar **last_sid, gchar** last_pwsalt_v2, GError **err) { GError *local_err = NULL; gc_free gchar *cipher = NULL; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(un != NULL, FALSE); g_return_val_if_fail(pw != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); mega_session_close(s); s->password_key_save = make_password_key(pw); // calculate cache file path gc_free gchar *un_lower = g_ascii_strdown(un, -1); gc_checksum_free GChecksum *cs = g_checksum_new(G_CHECKSUM_SHA1); g_checksum_update(cs, un_lower, -1); gc_free gchar *filename = g_strconcat(g_checksum_get_string(cs), ".megatools.cache", NULL); gc_free gchar *path = g_build_filename(g_get_tmp_dir(), filename, NULL); // load cipher data if (!g_file_get_contents(path, &cipher, NULL, &local_err)) { g_propagate_error(err, local_err); return FALSE; } // calculate password key gsize len = 0; gc_free gchar *data = b64_aes128_cbc_decrypt(cipher, s->password_key_save, &len); if (!data || len < 4) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Corrupted cache file"); return FALSE; } if (memcmp(data, "MEGA", 4) != 0) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Incorrect password"); return FALSE; } if (!s_json_is_valid(data + 4)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Corrupted cache file"); return FALSE; } gc_free gchar *cache_obj = s_json_get(data + 4); if (mega_debug & MEGA_DEBUG_CACHE) print_node(cache_obj, "LOAD CACHE: "); if (s_json_get_type(cache_obj) == S_JSON_TYPE_OBJECT) { gint64 version = s_json_get_member_int(cache_obj, "version", 0); gint64 last_refresh = s_json_get_member_int(cache_obj, "last_refresh", 0); if (version != CACHE_FORMAT_VERSION) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Cache version mismatch"); return FALSE; } // return sid value if available gc_free gchar *sid = s_json_get_member_string(cache_obj, "sid"); if (last_sid) *last_sid = g_strdup(sid); gc_free gchar *password_salt_v2 = s_json_get_member_string(cache_obj, "password_salt_v2"); if (last_pwsalt_v2) *last_pwsalt_v2 = g_strdup(password_salt_v2); // check max_age if (max_age > 0) { if (!last_refresh || ((last_refresh + max_age) < time(NULL))) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Cache timed out"); return FALSE; } } // cache is valid, load it gsize len; s->last_refresh = last_refresh; s->sid = sid; sid = NULL; s->password_salt_v2 = password_salt_v2; password_salt_v2 = NULL; s->password_key = s_json_get_member_bytes(cache_obj, "password_key", &len); s->master_key = s_json_get_member_bytes(cache_obj, "master_key", &len); s_json_get_member_rsa_key(cache_obj, "rsa_key", &s->rsa_key); s->user_handle = s_json_get_member_string(cache_obj, "user_handle"); s->user_name = s_json_get_member_string(cache_obj, "user_name"); s->user_email = s_json_get_member_string(cache_obj, "user_email"); if (!s->sid || !s->password_key || !s->master_key || !s->user_handle || !s->user_email || !s->rsa_key.p || !s->rsa_key.q || !s->rsa_key.d || !s->rsa_key.u) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Incomplete cache data"); return FALSE; } const gchar *sk_nodes = s_json_get_member(cache_obj, "share_keys"); if (s_json_get_type(sk_nodes) == S_JSON_TYPE_ARRAY) { S_JSON_FOREACH_ELEMENT(sk_nodes, sk_node) gc_free gchar *handle = s_json_get_member_string(sk_node, "handle"); gc_free guchar *key = s_json_get_member_bytes(sk_node, "key", &len); add_share_key(s, handle, key); S_JSON_FOREACH_END() } const gchar *fs_nodes = s_json_get_member(cache_obj, "fs_nodes"); if (s_json_get_type(fs_nodes) == S_JSON_TYPE_ARRAY) { S_JSON_FOREACH_ELEMENT(fs_nodes, fs_node) struct mega_node *n = g_new0(struct mega_node, 1); n->type = -1; S_JSON_FOREACH_MEMBER(fs_node, k, v) if (s_json_string_match(k, "name")) { n->name = s_json_get_string(v); n->name_collate_key = g_utf8_collate_key_for_filename(n->name, -1); } else if (s_json_string_match(k, "handle")) n->handle = s_json_get_string(v); else if (s_json_string_match(k, "parent_handle")) n->parent_handle = s_json_get_string(v); else if (s_json_string_match(k, "user_handle")) n->user_handle = s_json_get_string(v); else if (s_json_string_match(k, "su_handle")) n->su_handle = s_json_get_string(v); else if (s_json_string_match(k, "key")) n->key = s_json_get_bytes(v, &n->key_len); else if (s_json_string_match(k, "type")) n->type = s_json_get_int(v, -1); else if (s_json_string_match(k, "size")) n->size = s_json_get_int(v, 0); else if (s_json_string_match(k, "timestamp")) n->timestamp = s_json_get_int(v, 0); else if (s_json_string_match(k, "link")) n->link = s_json_get_string(v); S_JSON_FOREACH_END() s->fs_nodes = g_slist_prepend(s->fs_nodes, n); S_JSON_FOREACH_END() s->fs_nodes = g_slist_reverse(s->fs_nodes); } } else { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Corrupt cache"); return FALSE; } build_node_tree(s); return TRUE; } // }}} // {{{ mega_session_register gboolean mega_session_register(struct mega_session *s, const gchar *email, const gchar *password, const gchar *name, struct mega_reg_state **state, GError **err) { GError *local_err = NULL; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(email != NULL, FALSE); g_return_val_if_fail(password != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); g_return_val_if_fail(state != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); // logout mega_session_close(s); // create new master key gc_free guchar *master_key = make_random_key(); // create password key gc_free guchar *password_key = make_password_key(password); // create username hash //gc_free gchar* email_lower = g_ascii_strdown(email, -1); //gc_free gchar* uh = make_username_hash(email_lower, password_key); // create ssc (session self challenge) and ts gc_free guchar *ssc = make_random_key(); guchar ts_data[32]; memcpy(ts_data, ssc, 16); aes128_encrypt(ts_data + 16, ts_data, 16, master_key); // create anon user - [{"a":"up","k":"cHl8JeeSqgBOiURQL_Dvug","ts":"W9fg4kOw8p44KWoWICbgEd3rfMovr5HoSjI1vN7845s"}] -> ["-a1DHeWfguY"] gc_free gchar *node = api_call(s, 's', NULL, &local_err, "[{a:up, k:%S, ts:%S}]", b64_aes128_encrypt(master_key, 16, password_key), base64urlencode(ts_data, 32)); if (!node) { g_propagate_error(err, local_err); return FALSE; } gc_free gchar *user_handle = s_json_get_string(node); // login as an anon user - [{"a":"us","user":"-a1DHeWfguY"}] -> [{"tsid":"W9fg4kOw8p44KWoWICbgES1hMURIZVdmZ3VZ3et8yi-vkehKMjW83vzjmw","k":"cHl8JeeSqgBOiURQL_Dvug"}] g_free(node); node = api_call(s, 'o', NULL, &local_err, "[{a:us, user:%s}]", user_handle); if (!node) { g_propagate_error(err, local_err); return FALSE; } // from now on, tsid is used as session ID s->sid = s_json_get_member_string(node, "tsid"); // get user info - [{"a":"ug"}] -> [{"u":"-a1DHeWfguY","s":1,"n":0,"k":"cHl8JeeSqgBOiURQL_Dvug","c":0,"ts":"W9fg4kOw8p44KWoWICbgEd3rfMovr5HoSjI1vN7845s"}] g_free(node); node = api_call(s, 'o', NULL, &local_err, "[{a:ug}]"); if (!node) { g_propagate_error(err, local_err); return FALSE; } // set user name - [{"a":"up","name":"Bob Brown"}] -> ["-a1DHeWfguY"] g_free(node); node = api_call(s, 's', NULL, &local_err, "[{a:up, name:%s}]", name); if (!node) { g_propagate_error(err, local_err); return FALSE; } // request signup link - [{"a":"uc","c":"ZOB7VJrNXFvCzyZBIcdWhr5l4dJatrWpEjEpAmH17ic","n":"Qm9iIEJyb3du","m":"bWVnb3VzQGVtYWlsLmN6"}] -> [0] guchar c_data[32] = { 0 }; // aes(master_key, pw_key) + aes(verify, pw_key) memcpy(c_data, master_key, 16); RAND_bytes(c_data + 16, 4); RAND_bytes(c_data + 16 + 12, 4); // this will set new k from the first 16 bytes of c g_free(node); node = api_call(s, 'i', NULL, &local_err, "[{a:uc, c:%S, n:%S, m:%S}]", b64_aes128_encrypt(c_data, 32, password_key), base64urlencode(name, strlen(name)), base64urlencode(email, strlen(email))); if (!node) { g_propagate_error(err, local_err); return FALSE; } // save state struct mega_reg_state *st = *state = g_new0(struct mega_reg_state, 1); st->user_handle = user_handle; user_handle = NULL; memcpy(st->password_key, password_key, 16); memcpy(st->challenge, c_data + 16, 16); return TRUE; } // }}} // {{{ mega_session_register_verify gboolean mega_session_register_verify(struct mega_session *s, struct mega_reg_state *state, const gchar *signup_key, GError **err) { GError *local_err = NULL; gc_free gchar *node = NULL; g_return_val_if_fail(s != NULL, FALSE); g_return_val_if_fail(state != NULL, FALSE); g_return_val_if_fail(state->user_handle != NULL, FALSE); g_return_val_if_fail(signup_key != NULL, FALSE); g_return_val_if_fail(err == NULL || *err == NULL, FALSE); mega_session_close(s); // generate RSA key struct rsa_key key; memset(&key, 0, sizeof(key)); if (!rsa_key_gen(&key)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't generate RSA key"); return FALSE; } // u_types: // 0: not registered (!u.email) // 1: not sent confirmation email (!u.c) // 2: not yet set RSA key (!u.privk) // 3: full account // login as an anon user - [{"a":"us","user":"-a1DHeWfguY"}] -> [{"tsid":"W9fg4kOw8p44KWoWICbgES1hMURIZVdmZ3VZ3et8yi-vkehKMjW83vzjmw","k":"cHl8JeeSqgBOiURQL_Dvug"}] node = api_call(s, 'o', NULL, &local_err, "[{a:us, user:%s}]", state->user_handle); if (!node) { g_propagate_error(err, local_err); return FALSE; } // from now on, tsid is used as session ID s->sid = s_json_get_member_string(node, "tsid"); // send confirmation // // https://mega.nz/#confirmZOB7VJrNXFvCzyZBIcdWhr5l4dJatrWpEjEpAmH17ieRRWFjWAUAtSqaVQ_TQKltZWdvdXNAZW1haWwuY3oJQm9iIEJyb3duMhVh8n67rBg // // [{"a":"ud","c":"ZOB7VJrNXFvCzyZBIcdWhr5l4dJatrWpEjEpAmH17ieRRWFjWAUAtSqaVQ_TQKltZWdvdXNAZW1haWwuY3oJQm9iIEJyb3duMhVh8n67rBg"}] // // -> [["bWVnb3VzQGVtYWlsLmN6","Qm9iIEJyb3du","-a1DHeWfguY","ZOB7VJrNXFvCzyZBIcdWhg","vmXh0lq2takSMSkCYfXuJw"]] // ^ ^ ^ ^ ^ // email name handle enc(master_key, pwkey) enc(challenge, pwkey) g_free(node); node = api_call(s, 'a', NULL, &local_err, "[{a:ud, c:%s}]", signup_key); if (!node) { g_propagate_error(err, local_err); return FALSE; } gc_free gchar **arr = s_json_get_elements(node); if (g_strv_length(arr) != 5) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Wrong number of elements in retval from 'ud' (%d)", g_strv_length(arr)); return FALSE; } gc_free gchar *b64_email = s_json_get_string(arr[0]); gc_free gchar *b64_name = s_json_get_string(arr[1]); gc_free gchar *b64_master_key = s_json_get_string(arr[3]); gc_free gchar *b64_challenge = s_json_get_string(arr[4]); if (b64_email == NULL || b64_name == NULL || b64_master_key == NULL || b64_challenge == NULL) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid retval type from 'ud'"); return FALSE; } gsize len; gc_free gchar *email = base64urldecode(b64_email, &len); gc_free gchar *name = base64urldecode(b64_name, &len); gc_free guchar *master_key = b64_aes128_decrypt(b64_master_key, state->password_key, NULL); gc_free guchar *challenge = b64_aes128_decrypt(b64_challenge, state->password_key, NULL); if (email == NULL || name == NULL || master_key == NULL || challenge == NULL) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid retval type from 'ud'"); return FALSE; } // check challenge response if (memcmp(challenge, state->challenge, 16) != 0) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Invalid challenge response"); return FALSE; } // create username hash gc_free gchar *email_lower = g_ascii_strdown(email, -1); gc_free gchar *uh = make_username_hash(email_lower, state->password_key); // save uh and c // [{"uh":"VcWbhpU9cb0","c":"ZOB7VJrNXFvCzyZBIcdWhr5l4dJatrWpEjEpAmH17ieRRWFjWAUAtSqaVQ_TQKltZWdvdXNAZW1haWwuY3oJQm9iIEJyb3duMhVh8n67rBg","a":"up"}] -> ["-a1DHeWfguY"] g_free(node); node = api_call(s, 's', NULL, &local_err, "[{a:up, c:%s, uh:%s}]", signup_key, uh); if (!node) { g_propagate_error(err, local_err); return FALSE; } // relogin using email + uh // [{"a":"us","user":"megous@email.cz","uh":"VcWbhpU9cb0"}] -> [{"tsid":"W9fg4kOw8p44KWoWICbgES1hMURIZVdmZ3VZ3et8yi-vkehKMjW83vzjmw","k":"ZOB7VJrNXFvCzyZBIcdWhg"}] g_free(node); node = api_call(s, 'o', NULL, &local_err, "[{a:us, user:%s, uh:%s}]", email_lower, uh); if (!node) { g_propagate_error(err, local_err); return FALSE; } g_free(s->sid); s->sid = s_json_get_member_string(node, "tsid"); // set RSA key pair // [{"a":"up", "privk":"...", "pubk":"..."}] -> ["-a1DHeWfguY"] g_free(node); node = api_call(s, 's', NULL, &local_err, "[{a:up, pubk:%S, privk:%S}]", b64_encode_pubk(&key), b64_aes128_encrypt_privk(master_key, &key)); if (!node) { g_propagate_error(err, local_err); return FALSE; } //[{"a":"ug"}] -> [{"u":"-a1DHeWfguY","s":1,"email":"megous@email.cz","name":"Bob Brown","k":"ZOB7VJrNXFvCzyZBIcdWhg","c":1,"ts":"W9fg4kOw8p44KWoWICbgEd3rfMovr5HoSjI1vN7845s"}] //[{"a":"up","privk":"KSUujv7KB8QYL2At2sWeMPi2DQd_e09FwbR2RwileC9NxYw0MxFTKFj7Yxha__borDmBUacxaWXRCnMnAmMlsyWc8zw_ml9tysYHOsL4cQEzpBJtCCIrhnRjwnQk8JUVK--5fyQRS6G2RiOVdeFjkKQyifmXgBsiAlhHKhzSY0VD6ruR9htGfDsgImim_S-MzuWaHQN8TJkBkSZRAgXy6O2tUh0Bk4aEp8NaEV0GHdV7ec1S1jbR9FzwKB7cNkKxk2nd7wRS9rnl_QPz4MTv84dS6qHxahT0ebU5njC2_IkFLxuVlloyO2UTPRPHa9JHaPa3R2BrEmb-eWMmsZ5icNwJl8PLzuc9YlSI09-IR5rHZLm2uW-V05GI1IHIjw9LGzqli6WL7tzlMVhHrsq-xj70iXjVvOXJ3XhwbbW99S8O-3sQ2gG36fSHUcg0WMSD-8KiD-DhmhfqX8iqg-2YDfXrsYUNhq_VHJF83Zm0itPdRIkgUtBR9MFdASSPe_8uxlEBsCATTHNGIWbH0wiKRo2tEqbUTZCJjXhAyTyMhdjbSdS1ARKNr12YHkLKi12uhIJRO73VJGmjZD8De59cPduGihLGt3ipIKVIxsm-Xy6f9p29BtDHE1go_yacqbW1n8d1anN8WhmG8Q_1PwY1h-opagpd-Nf0geFti_3PI8dY75NPAuDyknv0kgn8OZ4ItzO10-4H3_GgLa5m8zb6usk-eeVCo4lkC4Z2YHHlY4YLRIL7rWC0m_kFcsyvVi1-PVNJ8GauLt9PYmW9hj20yJLwCYkEVSQyM4Yxgh55hSa3La3FnUt3Nls_ImOdcDWtYpB0UKJSKN_IYH4NlD60VwvFUifJndRB_JlJGvqzR4s","pubk":"B_9lGyG4ImN-3idVOARGr6dk-4Nn6VwVYxCTSk1nDvXztCNQ-eFwxIJoS3ykODSH_AjHhst_Loj_erSgX-AUOBAjkh5rQuriA4ciT76tIh_IarC5Yf2Zey8Ao_gLPgaqrLTIWPxDhSAmCLd3pa3X9weAuGK_7eiVxmXU4tK_5j7dyn949C4OMNhxp9vRgZqaOzcjouwKm8xH9nWqXTR7F2WKW2BcXxeBkRnFVJz6cd5IqmJENabhDH1-UDf9eCW7GeD2MHU8xnbJk2fXqnru35nxz9OG6VvVDMzrS6dtQU8mC7xnIut_N6eyMRWsHpm8N1bSxHgz1XWCodnOBHFIJSoJAAUR"}] -> ["-a1DHeWfguY"] return TRUE; } // }}} // {{{ Compatibility: old interfaces struct mega_node *mega_session_put_compat(struct mega_session *s, const gchar *remote_path, const gchar *local_path, GError **err) { GError *local_err = NULL; struct mega_node *node, *parent_node; gc_object_unref GFile *file = NULL; gc_free gchar *file_name = NULL; gc_free gchar *file_path = NULL; gc_object_unref GFileInputStream *stream = NULL; node = mega_session_stat(s, remote_path); if (node) { // reote path exists if (node->type == MEGA_NODE_FILE) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_EXISTS, "File already exists: %s", remote_path); return NULL; } else { // it's a directory, so we need to check if file with // basename(local_path) already exists there parent_node = node; // remote filename will be a basename(local_path) file_name = g_path_get_basename(local_path); gc_free gchar *tmp = g_strconcat(remote_path, "/", file_name, NULL); node = mega_session_stat(s, tmp); if (node) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_EXISTS, "File already exists: %s", tmp); return NULL; } } } else { // remote path doesn't exists, check the parent dir gc_free gchar *tmp = path_simplify(remote_path); gc_free gchar *parent_path = g_path_get_dirname(tmp); // remote filename will be a basename(remote_path) file_name = g_path_get_basename(tmp); if (!strcmp(parent_path, "/")) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't upload to toplevel dir: %s", remote_path); return NULL; } parent_node = mega_session_stat(s, parent_path); if (!parent_node || parent_node->type == MEGA_NODE_FILE) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Parent directory doesn't exist: %s", parent_path); return NULL; } } if (!mega_node_is_writable(s, parent_node) || parent_node->type == MEGA_NODE_NETWORK || parent_node->type == MEGA_NODE_CONTACT) { gchar path[4096]; if (!mega_node_get_path(parent_node, path, sizeof(path))) snprintf(path, sizeof path, "???"); g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Directory is not writable: %s", path); return NULL; } file = g_file_new_for_path(local_path); stream = g_file_read(file, NULL, &local_err); if (!stream) { g_propagate_prefixed_error(err, local_err, "Can't read local file %s: ", local_path); return NULL; } return mega_session_put(s, parent_node, file_name, stream, local_path, err); } gboolean mega_session_get_compat(struct mega_session *s, const gchar *local_path, const gchar *remote_path, GError **err) { gc_object_unref GFile *file = NULL; struct mega_node *node = mega_session_stat(s, remote_path); if (!node) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Remote file not found: %s", remote_path); return FALSE; } if (local_path) { file = g_file_new_for_path(local_path); if (g_file_query_exists(file, NULL)) { if (g_file_query_file_type(file, 0, NULL) == G_FILE_TYPE_DIRECTORY) { gc_object_unref GFile *child = g_file_get_child(file, node->name); if (g_file_query_exists(child, NULL)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_EXISTS, "Local file already exists: %s/%s", local_path, node->name); return FALSE; } else { file = child; child = NULL; } } else { g_set_error(err, MEGA_ERROR, MEGA_ERROR_EXISTS, "Local file already exists: %s", local_path); return FALSE; } } } return mega_session_get(s, file, node, err); } gboolean mega_session_dl_compat(struct mega_session *s, const gchar *handle, const gchar *key, const gchar *local_path, GError **err) { GError *local_err = NULL; gc_object_unref GFile *file = NULL, *parent_dir = NULL; if (local_path) { // get dir and filename to download to file = g_file_new_for_path(local_path); if (g_file_query_exists(file, NULL)) { if (g_file_query_file_type(file, 0, NULL) != G_FILE_TYPE_DIRECTORY) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_EXISTS, "File already exists: %s", local_path); return FALSE; } else { parent_dir = file; file = NULL; } } else { parent_dir = g_file_get_parent(file); if (g_file_query_file_type(parent_dir, 0, NULL) != G_FILE_TYPE_DIRECTORY) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_OTHER, "Can't download file into: %s", g_file_get_path(parent_dir)); return FALSE; } } } struct mega_download_data_params params = {}; if (!mega_session_dl_prepare(s, ¶ms, handle, key, &local_err)) { g_propagate_error(err, local_err); return FALSE; } if (local_path) { if (!file) file = g_file_get_child(parent_dir, params.node_name); if (g_file_query_exists(file, NULL)) { g_set_error(err, MEGA_ERROR, MEGA_ERROR_EXISTS, "Local file already exists: %s/%s", local_path, params.node_name); mega_download_data_free(¶ms); return FALSE; } } gboolean status = mega_session_download_data(s, ¶ms, file, err); mega_download_data_free(¶ms); return status; } // }}} // {{{ mega_cleanup void mega_cleanup(void) { tman_fini(); http_cleanup(); } // }}} megatools-1.11.3.20250203/lib/mega.h000066400000000000000000000147361474777720000163450ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __OLD_MEGA_H #define __OLD_MEGA_H #include #include // API error domain #define MEGA_ERROR mega_error_quark() enum { MEGA_ERROR_NO_HANDLE, MEGA_ERROR_EXISTS, MEGA_ERROR_OTHER }; // status callback enum { MEGA_STATUS_PROGRESS = 1, MEGA_STATUS_FILEINFO, MEGA_STATUS_DATA }; struct mega_status_data { gint type; union { struct { gint64 total; gint64 done; } progress; struct { const gchar *name; guint64 size; } fileinfo; struct { const guchar *buf; guint64 size; } data; }; }; typedef void (*mega_status_callback)(struct mega_status_data *data, gpointer userdata); // session data types enum { MEGA_NODE_FILE = 0, MEGA_NODE_FOLDER = 1, MEGA_NODE_ROOT = 2, MEGA_NODE_INBOX = 3, MEGA_NODE_TRASH = 4, MEGA_NODE_NETWORK = 9, MEGA_NODE_CONTACT = 8 }; struct mega_share_key { gchar *node_handle; guchar *key; }; struct mega_node { gchar *name; gchar *name_collate_key; gchar *handle; gchar *parent_handle; gchar *user_handle; gchar *su_handle; gsize key_len; guchar *key; gint type; guint64 size; glong timestamp; // call addlinks after refresh to get links populated gchar *link; struct mega_session *s; struct mega_node *parent; }; struct mega_user_quota { guint64 total; guint64 used; }; struct mega_reg_state { gchar *user_handle; guchar password_key[16]; guchar challenge[16]; }; struct mega_download_data_params { guchar node_key[32]; gchar *download_url; gchar *node_handle; gchar *node_name; guint64 node_size; }; #define MEGA_DEBUG_API 0x01 #define MEGA_DEBUG_CACHE 0x02 #define MEGA_DEBUG_FS 0x04 #define MEGA_DEBUG_HTTP 0x08 #define MEGA_DEBUG_TMAN 0x10 extern gint mega_debug; GQuark mega_error_quark(void); struct mega_session *mega_session_new(void); void mega_session_free(struct mega_session *s); void mega_session_set_speed(struct mega_session *s, gint ul, gint dl); void mega_session_set_workers(struct mega_session *s, gint workers); void mega_session_set_proxy(struct mega_session *s, const gchar *proxy); void mega_session_set_resume(struct mega_session *s, gboolean enabled); void mega_session_watch_status(struct mega_session *s, mega_status_callback cb, gpointer userdata); void mega_session_enable_previews(struct mega_session *s, gboolean enable); // this has side effect of the current session being closed gboolean mega_session_open(struct mega_session *s, const gchar *un, const gchar *pw, gint max_age, gboolean *is_new_session, GError **err); void mega_session_close(struct mega_session *s); const gchar *mega_session_get_sid(struct mega_session *s); gboolean mega_session_save(struct mega_session *s, GError **err); gboolean mega_session_get_user(struct mega_session *s, GError **err); gboolean mega_session_refresh(struct mega_session *s, GError **err); gboolean mega_session_addlinks(struct mega_session *s, GSList *nodes, GError **err); struct mega_user_quota *mega_session_user_quota(struct mega_session *s, GError **err); GSList *mega_session_ls_all(struct mega_session *s); GSList *mega_session_ls(struct mega_session *s, const gchar *path, gboolean recursive); GSList *mega_session_get_node_chilren(struct mega_session *s, struct mega_node *node); struct mega_node *mega_session_stat(struct mega_session *s, const gchar *path); struct mega_node *mega_session_mkdir(struct mega_session *s, const gchar *path, GError **err); gboolean mega_session_rm(struct mega_session *s, const gchar *path, GError **err); struct mega_node *mega_session_put(struct mega_session *s, struct mega_node *parent_node, const gchar* remote_name, GFileInputStream *stream, const gchar* local_path, GError **err); gchar *mega_session_new_node_attribute(struct mega_session *s, const guchar *data, gsize len, const gchar *type, const guchar *key, GError **err); gboolean mega_session_get(struct mega_session *s, GFile *file, struct mega_node *node, GError **err); gboolean mega_session_open_exp_folder(struct mega_session *s, const gchar *n, const gchar *key, const gchar *specific, GError **err); gboolean mega_session_dl_prepare(struct mega_session *s, struct mega_download_data_params *get_params, const gchar *handle, const gchar *key, GError **err); gboolean mega_session_download_data(struct mega_session *s, struct mega_download_data_params *params, GFile *file, GError **err); void mega_download_data_free(struct mega_download_data_params *params); struct mega_node* mega_session_get_node_by_handle(struct mega_session *s, const gchar* handle); gboolean mega_node_is_writable(struct mega_session *s, struct mega_node *n); gboolean mega_node_is_container(struct mega_node *n); gboolean mega_node_has_ancestor(struct mega_node *n, struct mega_node *ancestor); gchar *mega_node_get_link(struct mega_node *n, gboolean include_key); gchar *mega_node_get_key(struct mega_node *n); gboolean mega_node_get_path(struct mega_node *n, gchar *buf, gsize len); gchar *mega_node_get_path_dup(struct mega_node *n); gboolean mega_session_register(struct mega_session *s, const gchar *email, const gchar *password, const gchar *name, struct mega_reg_state **state, GError **err); gboolean mega_session_register_verify(struct mega_session *s, struct mega_reg_state *state, const gchar *signup_key, GError **err); // Compatibility / deprecated: struct mega_node *mega_session_put_compat(struct mega_session *s, const gchar *remote_path, const gchar *local_path, GError **err); gboolean mega_session_get_compat(struct mega_session *s, const gchar *local_path, const gchar *remote_path, GError **err); gboolean mega_session_dl_compat(struct mega_session *s, const gchar *handle, const gchar *key, const gchar *local_path, GError **err); void mega_cleanup(void); #endif megatools-1.11.3.20250203/lib/sjson.c000066400000000000000000000633771474777720000165700ustar00rootroot00000000000000/** * sjson - fast string based JSON parser/generator library * Copyright (C) 2013 Ondřej Jirman * * WWW: https://github.com/megous/sjson * * This program is free software: you can redistribute it and/or modify * it under the terms of the 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 . */ #include #include #include "sjson.h" enum { TOK_NONE = 0, // no more tokens TOK_OBJ_START, TOK_OBJ_END, TOK_ARRAY_START, TOK_ARRAY_END, TOK_COLON, TOK_COMMA, TOK_STRING, TOK_NOESC_STRING, TOK_NUMBER, TOK_FALSE, TOK_TRUE, TOK_NULL, TOK_INVALID }; /*!re2c re2c:define:YYCTYPE = "guchar"; re2c:define:YYCURSOR = c; re2c:define:YYMARKER = m; re2c:yyfill:enable = 0; re2c:yych:conversion = 1; re2c:indent:top = 1; WS = [ \t\n\r]+; INT = ("0" | [1-9] [0-9]*); MINUS = "-"; FRAC = ("." [0-9]+); EXP = [eE] [+-]? [0-9]+; NUMBER = MINUS? INT FRAC? EXP?; CHAR = [^\\"\000]; CTL = "\\" ["\\/bfnrt]; UNICODE = "\\u" [0-9a-fA-F]{4}; STRING = "\"" (CHAR|CTL|UNICODE)* "\""; NOESC_STRING = "\"" (CHAR)* "\""; IDENT = [A-Za-z_-][a-zA-Z0-9_-]*; */ static gint s_json_get_token(const gchar* json, const gchar** start, const gchar** end) { g_return_val_if_fail(json != NULL, FALSE); const guchar* c = (const guchar*)json; const guchar* m = NULL; const guchar* s; gint token; while (TRUE) { s = c; /*!re2c WS { continue; } "{" { token = TOK_OBJ_START; goto done; } "}" { token = TOK_OBJ_END; goto done; } "[" { token = TOK_ARRAY_START; goto done; } "]" { token = TOK_ARRAY_END; goto done; } NOESC_STRING { token = TOK_NOESC_STRING; goto done; } STRING { token = TOK_STRING; goto done; } ":" { token = TOK_COLON; goto done; } "," { token = TOK_COMMA; goto done; } NUMBER { token = TOK_NUMBER; goto done; } "true" { token = TOK_TRUE; goto done; } "false" { token = TOK_FALSE; goto done; } "null" { token = TOK_NULL; goto done; } [\000] { return TOK_NONE; } . | "\n" { return TOK_INVALID; } */ } done: if (start) *start = s; if (end) *end = c; return token; } static SJsonType token_to_type(gint token) { switch (token) { case TOK_NONE : return S_JSON_TYPE_NONE; case TOK_OBJ_START : return S_JSON_TYPE_OBJECT; case TOK_ARRAY_START : return S_JSON_TYPE_ARRAY; case TOK_STRING : return S_JSON_TYPE_STRING; case TOK_NOESC_STRING: return S_JSON_TYPE_STRING; case TOK_NUMBER : return S_JSON_TYPE_NUMBER; case TOK_FALSE : return S_JSON_TYPE_BOOL; case TOK_TRUE : return S_JSON_TYPE_BOOL; case TOK_NULL : return S_JSON_TYPE_NULL; default : return S_JSON_TYPE_INVALID; } } // public api static gboolean s_json_is_valid_inner(const gchar* json, const gchar** end) { const gchar* next; gint token; g_return_val_if_fail(json != NULL, FALSE); token = s_json_get_token(json, NULL, &next); if (token == TOK_ARRAY_START) { const gchar* array_elem = next; const gchar* array_next = NULL; gboolean expect_comma = FALSE; while (TRUE) { // check end of array token = s_json_get_token(array_elem, NULL, &array_next); if (token == TOK_ARRAY_END) { if (end) *end = array_next; return TRUE; } if (expect_comma) { if (token == TOK_COMMA) array_elem = array_next; // skip comma else return FALSE; } // check element if (!s_json_is_valid_inner(array_elem, &array_elem)) return FALSE; expect_comma = TRUE; } } else if (token == TOK_OBJ_START) { const gchar* obj_next = next; gboolean expect_comma = FALSE; while (TRUE) { // check end of object token = s_json_get_token(obj_next, NULL, &obj_next); if (token == TOK_OBJ_END) { if (end) *end = obj_next; return TRUE; } // eat comma if (expect_comma) { if (token != TOK_COMMA) return FALSE; token = s_json_get_token(obj_next, NULL, &obj_next); } // check member name and colon if (token != TOK_STRING && token != TOK_NOESC_STRING) return FALSE; token = s_json_get_token(obj_next, NULL, &obj_next); if (token != TOK_COLON) return FALSE; // check member value if (!s_json_is_valid_inner(obj_next, &obj_next)) return FALSE; expect_comma = TRUE; } } else if (token == TOK_STRING || token == TOK_NOESC_STRING || token == TOK_NUMBER || token == TOK_FALSE || token == TOK_TRUE || token == TOK_NULL) { if (end) *end = next; return TRUE; } return FALSE; } gboolean s_json_is_valid(const gchar* json) { const gchar* next; g_return_val_if_fail(json != NULL, FALSE); return s_json_is_valid_inner(json, &next) && s_json_get_token(next, NULL, NULL) == TOK_NONE; } gchar* s_json_get(const gchar* json) { const gchar* next; const gchar* start = NULL; g_return_val_if_fail(json != NULL, NULL); if (s_json_is_valid_inner(json, &next)) { s_json_get_token(json, &start, NULL); // must set start return g_strndup(start, next - start); } return NULL; } SJsonType s_json_get_type(const gchar* json) { g_return_val_if_fail(json != NULL, S_JSON_TYPE_INVALID); return token_to_type(s_json_get_token(json, NULL, NULL)); } const gchar* s_json_get_element_first(const gchar* json) { const gchar* next_elem; gint token; g_return_val_if_fail(json != NULL, NULL); token = s_json_get_token(json, NULL, &next_elem); if (token != TOK_ARRAY_START) return NULL; if (s_json_get_token(next_elem, NULL, NULL) == TOK_ARRAY_END) return NULL; return next_elem; } const gchar* s_json_get_element_next(const gchar* iter) { const gchar* next_elem; gint token; g_return_val_if_fail(iter != NULL, NULL); if (!s_json_is_valid_inner(iter, &next_elem)) return NULL; token = s_json_get_token(next_elem, NULL, &next_elem); if (token != TOK_COMMA) return NULL; return next_elem; } const gchar* s_json_get_element(const gchar* json, guint index) { guint current_index = 0; g_return_val_if_fail(json != NULL, NULL); S_JSON_FOREACH_ELEMENT(json, elem) if (current_index == index) return elem; current_index++; S_JSON_FOREACH_END() return NULL; } gchar** s_json_get_elements(const gchar* json) { GPtrArray* arr; g_return_val_if_fail(json != NULL, NULL); arr = g_ptr_array_sized_new(64); S_JSON_FOREACH_ELEMENT(json, elem) g_ptr_array_add(arr, (gchar*)elem); S_JSON_FOREACH_END() g_ptr_array_add(arr, NULL); return (gchar**)g_ptr_array_free(arr, FALSE); } const gchar* s_json_get_member_first(const gchar* json, const gchar** value) { const gchar* key; const gchar* colon; const gchar* val; gint token; g_return_val_if_fail(json != NULL, NULL); g_return_val_if_fail(value != NULL, NULL); if (s_json_get_token(json, NULL, &key) != TOK_OBJ_START) return NULL; token = s_json_get_token(key, NULL, &colon); if (token != TOK_STRING && token != TOK_NOESC_STRING) return NULL; if (s_json_get_token(colon, NULL, &val) != TOK_COLON) return NULL; *value = val; return key; } const gchar* s_json_get_member_next(const gchar** value) { const gchar* comma; const gchar* key; const gchar* colon; const gchar* val; gint token; g_return_val_if_fail(value != NULL && *value != NULL, NULL); if (!s_json_is_valid_inner(*value, &comma)) return NULL; if (s_json_get_token(comma, NULL, &key) != TOK_COMMA) return NULL; token = s_json_get_token(key, NULL, &colon); if (token != TOK_STRING && token != TOK_NOESC_STRING) return NULL; if (s_json_get_token(colon, NULL, &val) != TOK_COLON) return NULL; *value = val; return key; } const gchar* s_json_get_member(const gchar* json, const gchar* name) { g_return_val_if_fail(json != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); S_JSON_FOREACH_MEMBER(json, key, value) if (s_json_string_match(key, name)) return value; S_JSON_FOREACH_END() return NULL; } gchar* s_json_get_string(const gchar* json) { const gchar* start; const gchar* end; gint token; g_return_val_if_fail(json != NULL, NULL); token = s_json_get_token(json, &start, &end); if (token != TOK_STRING && token != TOK_NOESC_STRING) return NULL; if (token == TOK_NOESC_STRING) return g_strndup(start + 1, (end - start) - 2); GString* str = g_string_sized_new(end - start); const guchar* c = (const guchar*)start + 1; const guchar* m = NULL; const guchar* s; while (TRUE) { s = c; /*!re2c CHAR+ { g_string_append_len(str, s, c - s); continue; } CTL { gchar ch = (gchar)s[1]; if (ch == 'b') g_string_append_c(str, '\b'); else if (ch == 'n') g_string_append_c(str, '\n'); else if (ch == 'r') g_string_append_c(str, '\r'); else if (ch == 't') g_string_append_c(str, '\t'); else if (ch == 'f') g_string_append_c(str, '\f'); else g_string_append_c(str, ch); continue; } UNICODE { guint ch = 0; sscanf(s + 2, "%4x", &ch); g_string_append_unichar(str, ch); continue; } "\"" { return g_string_free(str, FALSE); } . | "\n" | [\000] { g_assert_not_reached(); } */ } return NULL; } gint64 s_json_get_int(const gchar* json, gint64 fallback) { const gchar* start; const gchar* end; g_return_val_if_fail(json != NULL, fallback); if (s_json_get_token(json, &start, &end) != TOK_NUMBER) return fallback; gchar* str = g_alloca(end - start + 1); memcpy(str, start, end - start); str[end - start] = 0; gint64 v = fallback; sscanf(str, "%" G_GINT64_FORMAT, &v); return v; } gdouble s_json_get_double(const gchar* json, gdouble fallback) { const gchar* start; const gchar* end; g_return_val_if_fail(json != NULL, fallback); if (s_json_get_token(json, &start, &end) != TOK_NUMBER) return fallback; gchar* str = g_alloca(end - start + 1); memcpy(str, start, end - start); str[end - start] = 0; gdouble v = fallback; sscanf(str, "%lf", &v); return v; } gboolean s_json_get_bool(const gchar* json) { g_return_val_if_fail(json != NULL, FALSE); gint token = s_json_get_token(json, NULL, NULL); if (token == TOK_TRUE) return TRUE; return FALSE; } gboolean s_json_is_null(const gchar* json) { g_return_val_if_fail(json != NULL, FALSE); gint token = s_json_get_token(json, NULL, NULL); if (token == TOK_NULL) return TRUE; return FALSE; } gboolean s_json_string_match(const gchar* json_str, const gchar* c_str) { const gchar *start, *end; gint token; g_return_val_if_fail(json_str != NULL, FALSE); g_return_val_if_fail(c_str != NULL, FALSE); token = s_json_get_token(json_str, &start, &end); if (token == TOK_NOESC_STRING) { // fast path gsize json_len = ((end - start) - 2); gint rs = strncmp(start + 1, c_str, json_len); return rs == 0 ? strlen(c_str) == json_len : FALSE; } else if (token == TOK_STRING) { // slow path gchar* str = s_json_get_string(json_str); gint rs = strcmp(str, c_str); g_free(str); return rs == 0; } else { return FALSE; } } // helpers gchar* s_json_get_member_string(const gchar* json, const gchar* name) { g_return_val_if_fail(json != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); const gchar* member = s_json_get_member(json, name); if (member) return s_json_get_string(member); return NULL; } gint64 s_json_get_member_int(const gchar* json, const gchar* name, gint64 fallback) { g_return_val_if_fail(json != NULL, fallback); g_return_val_if_fail(name != NULL, fallback); const gchar* member = s_json_get_member(json, name); if (member) return s_json_get_int(member, fallback); return fallback; } gdouble s_json_get_member_double(const gchar* json, const gchar* name, gdouble fallback) { g_return_val_if_fail(json != NULL, fallback); g_return_val_if_fail(name != NULL, fallback); const gchar* member = s_json_get_member(json, name); if (member) return s_json_get_double(member, fallback); return fallback; } gboolean s_json_get_member_bool(const gchar* json, const gchar* name) { g_return_val_if_fail(json != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); const gchar* member = s_json_get_member(json, name); if (member) return s_json_get_bool(member); return FALSE; } gboolean s_json_member_is_null(const gchar* json, const gchar* name) { g_return_val_if_fail(json != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); const gchar* member = s_json_get_member(json, name); if (member) return s_json_is_null(member); return TRUE; } // generator api struct _SJsonGen { GString* str; }; SJsonGen* s_json_gen_new(void) { SJsonGen* gen = g_slice_new(SJsonGen); gen->str = g_string_sized_new(512); return gen; } static void strip_comma(SJsonGen* json) { if (json->str->len > 0) { if (json->str->str[json->str->len - 1] == ',') g_string_truncate(json->str, json->str->len - 1); } } void s_json_gen_start_object(SJsonGen* json) { g_return_if_fail(json != NULL); g_string_append_c(json->str, '{'); } void s_json_gen_end_object(SJsonGen* json) { g_return_if_fail(json != NULL); strip_comma(json); g_string_append(json->str, "},"); } void s_json_gen_start_array(SJsonGen* json) { g_return_if_fail(json != NULL); g_string_append_c(json->str, '['); } void s_json_gen_end_array(SJsonGen* json) { g_return_if_fail(json != NULL); strip_comma(json); g_string_append(json->str, "],"); } static void escape_string(GString* str, const gchar* v) { g_return_if_fail(str != NULL); g_return_if_fail(v != NULL); const guchar* c = (const guchar*)v; const guchar* m = NULL; const guchar* s; g_string_append_c(str, '"'); while (TRUE) { s = c; /*!re2c [\n] { g_string_append(str, "\\n"); continue; } [\r] { g_string_append(str, "\\r"); continue; } [\b] { g_string_append(str, "\\b"); continue; } [\t] { g_string_append(str, "\\t"); continue; } [\f] { g_string_append(str, "\\f"); continue; } [\"] { g_string_append(str, "\\\""); continue; } [\\] { g_string_append(str, "\\\\"); continue; } [\000] { break; } [^\n\r\b\t"\\\000]+ { g_string_append_len(str, (gchar*)s, c - s); continue; } */ } g_string_append_c(str, '"'); } void s_json_gen_build(SJsonGen* json, const gchar* fmt, ...) { va_list args; gchar* v; g_return_if_fail(json != NULL); g_return_if_fail(fmt != NULL); va_start(args, fmt); v = s_json_buildv(fmt, args); va_end(args); s_json_gen_json(json, v); g_free(v); } void s_json_gen_json(SJsonGen* json, const gchar* v) { g_return_if_fail(json != NULL); if (v) { g_string_append(json->str, v); g_string_append_c(json->str, ','); } else s_json_gen_null(json); } void s_json_gen_string(SJsonGen* json, const gchar* v) { g_return_if_fail(json != NULL); if (v) { escape_string(json->str, v); g_string_append_c(json->str, ','); } else s_json_gen_null(json); } void s_json_gen_int(SJsonGen* json, gint64 v) { g_return_if_fail(json != NULL); g_string_append_printf(json->str, "%" G_GINT64_FORMAT ",", v); } void s_json_gen_double(SJsonGen* json, gdouble v) { g_return_if_fail(json != NULL); g_string_append_printf(json->str, "%lg,", v); } void s_json_gen_bool(SJsonGen* json, gboolean v) { g_return_if_fail(json != NULL); g_string_append(json->str, v ? "true," : "false,"); } void s_json_gen_null(SJsonGen* json) { g_return_if_fail(json != NULL); g_string_append(json->str, "null,"); } void s_json_gen_member_build(SJsonGen* json, const gchar* name, const gchar* fmt, ...) { va_list args; gchar* v; g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); g_return_if_fail(fmt != NULL); va_start(args, fmt); v = s_json_buildv(fmt, args); va_end(args); s_json_gen_member_json(json, name, v); g_free(v); } void s_json_gen_member_json(SJsonGen* json, const gchar* name, const gchar* v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_json(json, v); } void s_json_gen_member_string(SJsonGen* json, const gchar* name, const gchar* v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_string(json, v); } void s_json_gen_member_int(SJsonGen* json, const gchar* name, gint64 v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_int(json, v); } void s_json_gen_member_double(SJsonGen* json, const gchar* name, gdouble v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_double(json, v); } void s_json_gen_member_bool(SJsonGen* json, const gchar* name, gboolean v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_bool(json, v); } void s_json_gen_member_null(SJsonGen* json, const gchar* name) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_null(json); } void s_json_gen_member_array(SJsonGen* json, const gchar* name) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_start_array(json); } void s_json_gen_member_object(SJsonGen* json, const gchar* name) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_start_object(json); } gchar* s_json_gen_done(SJsonGen* json) { g_return_val_if_fail(json != NULL, NULL); strip_comma(json); gchar* str = g_string_free(json->str, FALSE); g_slice_free(SJsonGen, json); if (s_json_is_valid(str)) return str; g_free(str); return NULL; } // build // formats: // - % ? format // format: sidbnjSJ gchar* s_json_buildv(const gchar* format, va_list args) { g_return_val_if_fail(format != NULL, NULL); GString* str = g_string_sized_new(strlen(format)); const guchar* c = (const guchar*)format; const guchar* m = NULL; const guchar* s; #define IS_NULLABLE (s[1] == '?') #define FMT_FULL(arg_type, code_format, code_leave) \ gboolean is_null = (IS_NULLABLE ? va_arg(args, gboolean) : FALSE); \ gchar fmt = s[IS_NULLABLE ? 2 : 1]; \ arg_type arg = va_arg(args, arg_type); \ if (is_null) { \ g_string_append(str, "null"); \ } else { \ code_format \ } \ code_leave \ continue; #define FMT(arg_type, code) FMT_FULL(arg_type, code, ) while (TRUE) { s = c; /*!re2c (WS | "{" | "}" | "[" | "]" | NOESC_STRING | STRING | ":" | "," | NUMBER | "true" | "false" | "null")+ { g_string_append_len(str, s, c - s); continue; } IDENT { g_string_append_c(str, '"'); g_string_append_len(str, s, c - s); g_string_append_c(str, '"'); continue; } FMT = "%" "?"?; FMT [sS] { FMT_FULL(gchar*, if (arg) escape_string(str, arg); else g_string_append(str, "null");, if (fmt == 'S') g_free(arg);) } FMT [jJ] { FMT_FULL(gchar*, if (arg) { const gchar* end; if (s_json_is_valid_inner(arg, &end)) g_string_append_len(str, arg, end - arg); else g_string_append(str, "null"); } else { g_string_append(str, "null"); }, if (fmt == 'J') g_free(arg);) } FMT [i] { FMT(gint64, g_string_append_printf(str, "%" G_GINT64_FORMAT, arg);) } FMT [d] { FMT(gdouble, g_string_append_printf(str, "%lg" , arg);) } FMT [b] { FMT(gboolean, g_string_append(str, arg ? "true" : "false");) } [\000] { break; } . | "\n" { goto err; } */ } if (s_json_is_valid(str->str)) return g_string_free(str, FALSE); err: g_string_free(str, TRUE); return NULL; } gchar* s_json_build(const gchar* format, ...) { va_list args; gchar* json; g_return_val_if_fail(format != NULL, NULL); va_start(args, format); json = s_json_buildv(format, args); va_end(args); return json; } gchar* s_json_pretty(const gchar* json) { const gchar* start; const gchar* end; GString *str, *ind; gchar* json_valid; g_return_val_if_fail(json != NULL, NULL); // validate and isolate json_valid = s_json_get(json); if (!json_valid) return NULL; start = json_valid; str = g_string_sized_new(strlen(json) * 2); ind = g_string_sized_new(50); #define PUSH_INDENT() g_string_append_c(ind, '\t') #define POP_INDENT() g_string_truncate(ind, MAX(ind->len - 1, 0)) #define INDENT() g_string_append_len(str, ind->str, ind->len) gint prev_token = TOK_NONE; while (TRUE) { gint token = s_json_get_token(start, &start, &end); if (token == TOK_NONE) break; gint next_token = s_json_get_token(end, NULL, NULL); if ((token == TOK_OBJ_END && prev_token != TOK_OBJ_START) || (token == TOK_ARRAY_END && prev_token != TOK_ARRAY_START && prev_token != TOK_OBJ_END)) { g_string_append_c(str, '\n'); POP_INDENT(); INDENT(); } g_string_append_len(str, start, end - start); if ((token == TOK_OBJ_START && next_token != TOK_OBJ_END) || (token == TOK_ARRAY_START && next_token != TOK_OBJ_START && next_token != TOK_ARRAY_END)) { g_string_append_c(str, '\n'); PUSH_INDENT(); INDENT(); } else if (token == TOK_COMMA) { if ((prev_token == TOK_OBJ_END && next_token == TOK_OBJ_START) || (prev_token == TOK_ARRAY_END && next_token == TOK_ARRAY_START)) { g_string_append_c(str, ' '); } else { g_string_append_c(str, '\n'); INDENT(); } } else if (token == TOK_COLON) { g_string_append_c(str, ' '); } prev_token = token; start = end; } g_string_free(ind, TRUE); g_free(json_valid); return g_string_free(str, FALSE); } const gchar* s_json_path(const gchar* json, const gchar* path) { const guchar* c = (const guchar*)path; const guchar* m = NULL; const guchar* s; const gchar* cur_node = json; g_return_val_if_fail(json != NULL, NULL); g_return_val_if_fail(path != NULL, NULL); #define CHECK_TYPE(type) \ if (cur_node && s_json_get_type(cur_node) == type) \ return cur_node; \ if ((!cur_node || s_json_get_type(cur_node) == S_JSON_TYPE_NULL) && s[0] == '?') \ return "null"; \ return NULL; while (TRUE) { s = c; /*!re2c WS { // skip whitespace continue; } [\$] { // root cur_node = json; continue; } [.] IDENT { if (!cur_node || s_json_get_type(cur_node) != S_JSON_TYPE_OBJECT) return NULL; // zero terminate member name on stack gchar* name = g_alloca((c - s)); memcpy(name, s + 1, c - (s + 1)); name[c - (s + 1)] = '\0'; cur_node = s_json_get_member(cur_node, name); continue; } "[" INT "]" { if (!cur_node || s_json_get_type(cur_node) != S_JSON_TYPE_ARRAY) return NULL; guint index; sscanf(s + 1, "%u", &index); cur_node = s_json_get_element(cur_node, index); continue; } TYPE = [!?]; TYPE "n" "umber"? { CHECK_TYPE(S_JSON_TYPE_NUMBER) } TYPE "s" "tring"? { CHECK_TYPE(S_JSON_TYPE_STRING) } TYPE "i" "nteger"? { if (cur_node && s_json_get_type(cur_node) == S_JSON_TYPE_NUMBER) { const gchar *int_start, *int_end, *i; g_assert(s_json_get_token(cur_node, &int_start, &int_end) == TOK_NUMBER); for (i = int_start; i < int_end; i++) if (*i > '9' || *i < '0') return NULL; return cur_node; } if ((!cur_node || s_json_get_type(cur_node) == S_JSON_TYPE_NULL) && s[0] == '?') return "null"; return NULL; } TYPE "b" "oolean"? { CHECK_TYPE(S_JSON_TYPE_BOOL) } TYPE "o" "bject"? { CHECK_TYPE(S_JSON_TYPE_OBJECT) } TYPE "a" "rray"? { CHECK_TYPE(S_JSON_TYPE_ARRAY) } [\000] { break; } . | "\n" { return NULL; } */ } return cur_node; } gchar* s_json_compact(const gchar* json) { g_return_val_if_fail(json != NULL, NULL); GString* str = g_string_sized_new(strlen(json)); const guchar* c = (const guchar*)json; const guchar* m = NULL; const guchar* s; while (TRUE) { s = c; /*!re2c ("{" | "}" | "[" | "]" | NOESC_STRING | STRING | ":" | "," | NUMBER | "true" | "false" | "null")+ { g_string_append_len(str, s, c - s); continue; } WS { continue; } [\000] { break; } . | "\n" { goto err; } */ } return g_string_free(str, FALSE); err: g_string_free(str, TRUE); return NULL; } megatools-1.11.3.20250203/lib/sjson.gen.c000066400000000000000000002601141474777720000173240ustar00rootroot00000000000000/* Generated by re2c 0.13.5 on Sat Apr 20 16:07:45 2013 */ #line 1 "sjson.c" /** * sjson - fast string based JSON parser/generator library * Copyright (C) 2013 Ondřej Jirman * * WWW: https://github.com/megous/sjson * * This program is free software: you can redistribute it and/or modify * it under the terms of the 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 . */ #include #include #include "sjson.h" enum { TOK_NONE = 0, // no more tokens TOK_OBJ_START, TOK_OBJ_END, TOK_ARRAY_START, TOK_ARRAY_END, TOK_COLON, TOK_COMMA, TOK_STRING, TOK_NOESC_STRING, TOK_NUMBER, TOK_FALSE, TOK_TRUE, TOK_NULL, TOK_INVALID }; #line 66 "sjson.c" static gint s_json_get_token(const gchar* json, const gchar** start, const gchar** end) { g_return_val_if_fail(json != NULL, FALSE); const guchar* c = (const guchar*)json; const guchar* m = NULL; const guchar* s; gint token; while (TRUE) { s = c; #line 63 "sjson.gen.c" { guchar yych; unsigned int yyaccept = 0; yych = (guchar)*c; if (yych <= '9') { if (yych <= ' ') { if (yych <= '\n') { if (yych <= 0x00) goto yy25; if (yych <= 0x08) goto yy27; } else { if (yych == '\r') goto yy2; if (yych <= 0x1F) goto yy27; } } else { if (yych <= ',') { if (yych == '"') goto yy12; if (yych <= '+') goto yy27; goto yy16; } else { if (yych <= '-') goto yy18; if (yych <= '/') goto yy27; if (yych <= '0') goto yy19; goto yy21; } } } else { if (yych <= 'm') { if (yych <= '\\') { if (yych <= ':') goto yy14; if (yych == '[') goto yy8; goto yy27; } else { if (yych <= ']') goto yy10; if (yych == 'f') goto yy23; goto yy27; } } else { if (yych <= 'z') { if (yych <= 'n') goto yy24; if (yych == 't') goto yy22; goto yy27; } else { if (yych <= '{') goto yy4; if (yych == '}') goto yy6; goto yy27; } } } yy2: ++c; yych = (guchar)*c; goto yy66; yy3: #line 82 "sjson.c" { continue; } #line 122 "sjson.gen.c" yy4: ++c; #line 86 "sjson.c" { token = TOK_OBJ_START; goto done; } #line 130 "sjson.gen.c" yy6: ++c; #line 91 "sjson.c" { token = TOK_OBJ_END; goto done; } #line 138 "sjson.gen.c" yy8: ++c; #line 96 "sjson.c" { token = TOK_ARRAY_START; goto done; } #line 146 "sjson.gen.c" yy10: ++c; #line 101 "sjson.c" { token = TOK_ARRAY_END; goto done; } #line 154 "sjson.gen.c" yy12: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych >= 0x01) goto yy53; yy13: #line 150 "sjson.c" { return TOK_INVALID; } #line 164 "sjson.gen.c" yy14: ++c; #line 116 "sjson.c" { token = TOK_COLON; goto done; } #line 172 "sjson.gen.c" yy16: ++c; #line 121 "sjson.c" { token = TOK_COMMA; goto done; } #line 180 "sjson.gen.c" yy18: yych = (guchar)*++c; if (yych <= '/') goto yy13; if (yych <= '0') goto yy51; if (yych <= '9') goto yy42; goto yy13; yy19: yyaccept = 1; yych = (guchar)*(m = ++c); if (yych <= 'D') { if (yych == '.') goto yy44; } else { if (yych <= 'E') goto yy45; if (yych == 'e') goto yy45; } yy20: #line 126 "sjson.c" { token = TOK_NUMBER; goto done; } #line 202 "sjson.gen.c" yy21: yyaccept = 1; yych = (guchar)*(m = ++c); goto yy43; yy22: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych == 'r') goto yy38; goto yy13; yy23: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych == 'a') goto yy33; goto yy13; yy24: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych == 'u') goto yy28; goto yy13; yy25: ++c; #line 146 "sjson.c" { return TOK_NONE; } #line 228 "sjson.gen.c" yy27: yych = (guchar)*++c; goto yy13; yy28: yych = (guchar)*++c; if (yych == 'l') goto yy30; yy29: c = m; if (yyaccept <= 0) { goto yy13; } else { goto yy20; } yy30: yych = (guchar)*++c; if (yych != 'l') goto yy29; ++c; #line 141 "sjson.c" { token = TOK_NULL; goto done; } #line 251 "sjson.gen.c" yy33: yych = (guchar)*++c; if (yych != 'l') goto yy29; yych = (guchar)*++c; if (yych != 's') goto yy29; yych = (guchar)*++c; if (yych != 'e') goto yy29; ++c; #line 136 "sjson.c" { token = TOK_FALSE; goto done; } #line 265 "sjson.gen.c" yy38: yych = (guchar)*++c; if (yych != 'u') goto yy29; yych = (guchar)*++c; if (yych != 'e') goto yy29; ++c; #line 131 "sjson.c" { token = TOK_TRUE; goto done; } #line 277 "sjson.gen.c" yy42: yyaccept = 1; m = ++c; yych = (guchar)*c; yy43: if (yych <= '9') { if (yych == '.') goto yy44; if (yych <= '/') goto yy20; goto yy42; } else { if (yych <= 'E') { if (yych <= 'D') goto yy20; goto yy45; } else { if (yych == 'e') goto yy45; goto yy20; } } yy44: yych = (guchar)*++c; if (yych <= '/') goto yy29; if (yych <= '9') goto yy49; goto yy29; yy45: yych = (guchar)*++c; if (yych <= ',') { if (yych != '+') goto yy29; } else { if (yych <= '-') goto yy46; if (yych <= '/') goto yy29; if (yych <= '9') goto yy47; goto yy29; } yy46: yych = (guchar)*++c; if (yych <= '/') goto yy29; if (yych >= ':') goto yy29; yy47: ++c; yych = (guchar)*c; if (yych <= '/') goto yy20; if (yych <= '9') goto yy47; goto yy20; yy49: yyaccept = 1; m = ++c; yych = (guchar)*c; if (yych <= 'D') { if (yych <= '/') goto yy20; if (yych <= '9') goto yy49; goto yy20; } else { if (yych <= 'E') goto yy45; if (yych == 'e') goto yy45; goto yy20; } yy51: yyaccept = 1; yych = (guchar)*(m = ++c); if (yych <= 'D') { if (yych == '.') goto yy44; goto yy20; } else { if (yych <= 'E') goto yy45; if (yych == 'e') goto yy45; goto yy20; } yy52: ++c; yych = (guchar)*c; yy53: if (yych <= '"') { if (yych <= 0x00) goto yy29; if (yych <= '!') goto yy52; goto yy55; } else { if (yych != '\\') goto yy52; } yy54: ++c; yych = (guchar)*c; if (yych <= 'e') { if (yych <= '/') { if (yych == '"') goto yy58; if (yych <= '.') goto yy29; goto yy58; } else { if (yych <= '\\') { if (yych <= '[') goto yy29; goto yy58; } else { if (yych == 'b') goto yy58; goto yy29; } } } else { if (yych <= 'q') { if (yych <= 'f') goto yy58; if (yych == 'n') goto yy58; goto yy29; } else { if (yych <= 's') { if (yych <= 'r') goto yy58; goto yy29; } else { if (yych <= 't') goto yy58; if (yych <= 'u') goto yy57; goto yy29; } } } yy55: ++c; #line 106 "sjson.c" { token = TOK_NOESC_STRING; goto done; } #line 396 "sjson.gen.c" yy57: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy29; if (yych <= '9') goto yy62; goto yy29; } else { if (yych <= 'F') goto yy62; if (yych <= '`') goto yy29; if (yych <= 'f') goto yy62; goto yy29; } yy58: ++c; yych = (guchar)*c; if (yych <= '"') { if (yych <= 0x00) goto yy29; if (yych <= '!') goto yy58; } else { if (yych == '\\') goto yy54; goto yy58; } ++c; #line 111 "sjson.c" { token = TOK_STRING; goto done; } #line 426 "sjson.gen.c" yy62: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy29; if (yych >= ':') goto yy29; } else { if (yych <= 'F') goto yy63; if (yych <= '`') goto yy29; if (yych >= 'g') goto yy29; } yy63: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy29; if (yych >= ':') goto yy29; } else { if (yych <= 'F') goto yy64; if (yych <= '`') goto yy29; if (yych >= 'g') goto yy29; } yy64: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy29; if (yych <= '9') goto yy58; goto yy29; } else { if (yych <= 'F') goto yy58; if (yych <= '`') goto yy29; if (yych <= 'f') goto yy58; goto yy29; } yy65: ++c; yych = (guchar)*c; yy66: if (yych <= '\f') { if (yych <= 0x08) goto yy3; if (yych <= '\n') goto yy65; goto yy3; } else { if (yych <= '\r') goto yy65; if (yych == ' ') goto yy65; goto yy3; } } #line 153 "sjson.c" } done: if (start) *start = s; if (end) *end = c; return token; } static SJsonType token_to_type(gint token) { switch (token) { case TOK_NONE : return S_JSON_TYPE_NONE; case TOK_OBJ_START : return S_JSON_TYPE_OBJECT; case TOK_ARRAY_START : return S_JSON_TYPE_ARRAY; case TOK_STRING : return S_JSON_TYPE_STRING; case TOK_NOESC_STRING: return S_JSON_TYPE_STRING; case TOK_NUMBER : return S_JSON_TYPE_NUMBER; case TOK_FALSE : return S_JSON_TYPE_BOOL; case TOK_TRUE : return S_JSON_TYPE_BOOL; case TOK_NULL : return S_JSON_TYPE_NULL; default : return S_JSON_TYPE_INVALID; } } // public api static gboolean s_json_is_valid_inner(const gchar* json, const gchar** end) { const gchar* next; gint token; g_return_val_if_fail(json != NULL, FALSE); token = s_json_get_token(json, NULL, &next); if (token == TOK_ARRAY_START) { const gchar* array_elem = next; const gchar* array_next = NULL; gboolean expect_comma = FALSE; while (TRUE) { // check end of array token = s_json_get_token(array_elem, NULL, &array_next); if (token == TOK_ARRAY_END) { if (end) *end = array_next; return TRUE; } if (expect_comma) { if (token == TOK_COMMA) array_elem = array_next; // skip comma else return FALSE; } // check element if (!s_json_is_valid_inner(array_elem, &array_elem)) return FALSE; expect_comma = TRUE; } } else if (token == TOK_OBJ_START) { const gchar* obj_next = next; gboolean expect_comma = FALSE; while (TRUE) { // check end of object token = s_json_get_token(obj_next, NULL, &obj_next); if (token == TOK_OBJ_END) { if (end) *end = obj_next; return TRUE; } // eat comma if (expect_comma) { if (token != TOK_COMMA) return FALSE; token = s_json_get_token(obj_next, NULL, &obj_next); } // check member name and colon if (token != TOK_STRING && token != TOK_NOESC_STRING) return FALSE; token = s_json_get_token(obj_next, NULL, &obj_next); if (token != TOK_COLON) return FALSE; // check member value if (!s_json_is_valid_inner(obj_next, &obj_next)) return FALSE; expect_comma = TRUE; } } else if (token == TOK_STRING || token == TOK_NOESC_STRING || token == TOK_NUMBER || token == TOK_FALSE || token == TOK_TRUE || token == TOK_NULL) { if (end) *end = next; return TRUE; } return FALSE; } gboolean s_json_is_valid(const gchar* json) { const gchar* next; g_return_val_if_fail(json != NULL, FALSE); return s_json_is_valid_inner(json, &next) && s_json_get_token(next, NULL, NULL) == TOK_NONE; } gchar* s_json_get(const gchar* json) { const gchar* next; const gchar* start = NULL; g_return_val_if_fail(json != NULL, NULL); if (s_json_is_valid_inner(json, &next)) { s_json_get_token(json, &start, NULL); // must set start return g_strndup(start, next - start); } return NULL; } SJsonType s_json_get_type(const gchar* json) { g_return_val_if_fail(json != NULL, S_JSON_TYPE_INVALID); return token_to_type(s_json_get_token(json, NULL, NULL)); } const gchar* s_json_get_element_first(const gchar* json) { const gchar* next_elem; gint token; g_return_val_if_fail(json != NULL, NULL); token = s_json_get_token(json, NULL, &next_elem); if (token != TOK_ARRAY_START) return NULL; if (s_json_get_token(next_elem, NULL, NULL) == TOK_ARRAY_END) return NULL; return next_elem; } const gchar* s_json_get_element_next(const gchar* iter) { const gchar* next_elem; gint token; g_return_val_if_fail(iter != NULL, NULL); if (!s_json_is_valid_inner(iter, &next_elem)) return NULL; token = s_json_get_token(next_elem, NULL, &next_elem); if (token != TOK_COMMA) return NULL; return next_elem; } const gchar* s_json_get_element(const gchar* json, guint index) { guint current_index = 0; g_return_val_if_fail(json != NULL, NULL); S_JSON_FOREACH_ELEMENT(json, elem) if (current_index == index) return elem; current_index++; S_JSON_FOREACH_END() return NULL; } gchar** s_json_get_elements(const gchar* json) { GPtrArray* arr; g_return_val_if_fail(json != NULL, NULL); arr = g_ptr_array_sized_new(64); S_JSON_FOREACH_ELEMENT(json, elem) g_ptr_array_add(arr, (gchar*)elem); S_JSON_FOREACH_END() g_ptr_array_add(arr, NULL); return (gchar**)g_ptr_array_free(arr, FALSE); } const gchar* s_json_get_member_first(const gchar* json, const gchar** value) { const gchar* key; const gchar* colon; const gchar* val; gint token; g_return_val_if_fail(json != NULL, NULL); g_return_val_if_fail(value != NULL, NULL); if (s_json_get_token(json, NULL, &key) != TOK_OBJ_START) return NULL; token = s_json_get_token(key, NULL, &colon); if (token != TOK_STRING && token != TOK_NOESC_STRING) return NULL; if (s_json_get_token(colon, NULL, &val) != TOK_COLON) return NULL; *value = val; return key; } const gchar* s_json_get_member_next(const gchar** value) { const gchar* comma; const gchar* key; const gchar* colon; const gchar* val; gint token; g_return_val_if_fail(value != NULL && *value != NULL, NULL); if (!s_json_is_valid_inner(*value, &comma)) return NULL; if (s_json_get_token(comma, NULL, &key) != TOK_COMMA) return NULL; token = s_json_get_token(key, NULL, &colon); if (token != TOK_STRING && token != TOK_NOESC_STRING) return NULL; if (s_json_get_token(colon, NULL, &val) != TOK_COLON) return NULL; *value = val; return key; } const gchar* s_json_get_member(const gchar* json, const gchar* name) { g_return_val_if_fail(json != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); S_JSON_FOREACH_MEMBER(json, key, value) if (s_json_string_match(key, name)) return value; S_JSON_FOREACH_END() return NULL; } gchar* s_json_get_string(const gchar* json) { const gchar* start; const gchar* end; gint token; g_return_val_if_fail(json != NULL, NULL); token = s_json_get_token(json, &start, &end); if (token != TOK_STRING && token != TOK_NOESC_STRING) return NULL; if (token == TOK_NOESC_STRING) return g_strndup(start + 1, (end - start) - 2); GString* str = g_string_sized_new(end - start); const guchar* c = (const guchar*)start + 1; const guchar* m = NULL; const guchar* s; while (TRUE) { s = c; #line 791 "sjson.gen.c" { guchar yych; yych = (guchar)*c; if (yych <= '"') { if (yych <= 0x00) goto yy75; if (yych >= '"') goto yy73; } else { if (yych == '\\') goto yy71; } ++c; yych = (guchar)*c; goto yy86; yy70: #line 467 "sjson.c" { g_string_append_len(str, s, c - s); continue; } #line 810 "sjson.gen.c" yy71: yych = (guchar)*(m = ++c); if (yych <= 'e') { if (yych <= '/') { if (yych == '"') goto yy78; if (yych >= '/') goto yy78; } else { if (yych <= '\\') { if (yych >= '\\') goto yy78; } else { if (yych == 'b') goto yy78; } } } else { if (yych <= 'q') { if (yych <= 'f') goto yy78; if (yych == 'n') goto yy78; } else { if (yych <= 's') { if (yych <= 'r') goto yy78; } else { if (yych <= 't') goto yy78; if (yych <= 'u') goto yy76; } } } yy72: #line 502 "sjson.c" { g_assert_not_reached(); } #line 842 "sjson.gen.c" yy73: ++c; #line 498 "sjson.c" { return g_string_free(str, FALSE); } #line 849 "sjson.gen.c" yy75: yych = (guchar)*++c; goto yy72; yy76: yych = (guchar)*++c; if (yych <= '@') { if (yych <= '/') goto yy77; if (yych <= '9') goto yy80; } else { if (yych <= 'F') goto yy80; if (yych <= '`') goto yy77; if (yych <= 'f') goto yy80; } yy77: c = m; goto yy72; yy78: ++c; #line 472 "sjson.c" { gchar ch = (gchar)s[1]; if (ch == 'b') g_string_append_c(str, '\b'); else if (ch == 'n') g_string_append_c(str, '\n'); else if (ch == 'r') g_string_append_c(str, '\r'); else if (ch == 't') g_string_append_c(str, '\t'); else if (ch == 'f') g_string_append_c(str, '\f'); else g_string_append_c(str, ch); continue; } #line 887 "sjson.gen.c" yy80: yych = (guchar)*++c; if (yych <= '@') { if (yych <= '/') goto yy77; if (yych >= ':') goto yy77; } else { if (yych <= 'F') goto yy81; if (yych <= '`') goto yy77; if (yych >= 'g') goto yy77; } yy81: yych = (guchar)*++c; if (yych <= '@') { if (yych <= '/') goto yy77; if (yych >= ':') goto yy77; } else { if (yych <= 'F') goto yy82; if (yych <= '`') goto yy77; if (yych >= 'g') goto yy77; } yy82: yych = (guchar)*++c; if (yych <= '@') { if (yych <= '/') goto yy77; if (yych >= ':') goto yy77; } else { if (yych <= 'F') goto yy83; if (yych <= '`') goto yy77; if (yych >= 'g') goto yy77; } yy83: ++c; #line 491 "sjson.c" { guint ch = 0; sscanf(s + 2, "%4x", &ch); g_string_append_unichar(str, ch); continue; } #line 927 "sjson.gen.c" yy85: ++c; yych = (guchar)*c; yy86: if (yych <= '"') { if (yych <= 0x00) goto yy70; if (yych <= '!') goto yy85; goto yy70; } else { if (yych == '\\') goto yy70; goto yy85; } } #line 505 "sjson.c" } return NULL; } gint64 s_json_get_int(const gchar* json, gint64 fallback) { const gchar* start; const gchar* end; g_return_val_if_fail(json != NULL, fallback); if (s_json_get_token(json, &start, &end) != TOK_NUMBER) return fallback; gchar* str = g_alloca(end - start + 1); memcpy(str, start, end - start); str[end - start] = 0; gint64 v = fallback; sscanf(str, "%" G_GINT64_FORMAT, &v); return v; } gdouble s_json_get_double(const gchar* json, gdouble fallback) { const gchar* start; const gchar* end; g_return_val_if_fail(json != NULL, fallback); if (s_json_get_token(json, &start, &end) != TOK_NUMBER) return fallback; gchar* str = g_alloca(end - start + 1); memcpy(str, start, end - start); str[end - start] = 0; gdouble v = fallback; sscanf(str, "%lf", &v); return v; } gboolean s_json_get_bool(const gchar* json) { g_return_val_if_fail(json != NULL, FALSE); gint token = s_json_get_token(json, NULL, NULL); if (token == TOK_TRUE) return TRUE; return FALSE; } gboolean s_json_is_null(const gchar* json) { g_return_val_if_fail(json != NULL, FALSE); gint token = s_json_get_token(json, NULL, NULL); if (token == TOK_NULL) return TRUE; return FALSE; } gboolean s_json_string_match(const gchar* json_str, const gchar* c_str) { const gchar *start, *end; gint token; g_return_val_if_fail(json_str != NULL, FALSE); g_return_val_if_fail(c_str != NULL, FALSE); token = s_json_get_token(json_str, &start, &end); if (token == TOK_NOESC_STRING) { // fast path gsize json_len = ((end - start) - 2); gint rs = strncmp(start + 1, c_str, json_len); return rs == 0 ? strlen(c_str) == json_len : FALSE; } else if (token == TOK_STRING) { // slow path gchar* str = s_json_get_string(json_str); gint rs = strcmp(str, c_str); g_free(str); return rs == 0; } else { return FALSE; } } // helpers gchar* s_json_get_member_string(const gchar* json, const gchar* name) { g_return_val_if_fail(json != NULL, NULL); g_return_val_if_fail(name != NULL, NULL); const gchar* member = s_json_get_member(json, name); if (member) return s_json_get_string(member); return NULL; } gint64 s_json_get_member_int(const gchar* json, const gchar* name, gint64 fallback) { g_return_val_if_fail(json != NULL, fallback); g_return_val_if_fail(name != NULL, fallback); const gchar* member = s_json_get_member(json, name); if (member) return s_json_get_int(member, fallback); return fallback; } gdouble s_json_get_member_double(const gchar* json, const gchar* name, gdouble fallback) { g_return_val_if_fail(json != NULL, fallback); g_return_val_if_fail(name != NULL, fallback); const gchar* member = s_json_get_member(json, name); if (member) return s_json_get_double(member, fallback); return fallback; } gboolean s_json_get_member_bool(const gchar* json, const gchar* name) { g_return_val_if_fail(json != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); const gchar* member = s_json_get_member(json, name); if (member) return s_json_get_bool(member); return FALSE; } gboolean s_json_member_is_null(const gchar* json, const gchar* name) { g_return_val_if_fail(json != NULL, FALSE); g_return_val_if_fail(name != NULL, FALSE); const gchar* member = s_json_get_member(json, name); if (member) return s_json_is_null(member); return TRUE; } // generator api struct _SJsonGen { GString* str; }; SJsonGen* s_json_gen_new(void) { SJsonGen* gen = g_slice_new(SJsonGen); gen->str = g_string_sized_new(512); return gen; } static void strip_comma(SJsonGen* json) { if (json->str->len > 0) { if (json->str->str[json->str->len - 1] == ',') g_string_truncate(json->str, json->str->len - 1); } } void s_json_gen_start_object(SJsonGen* json) { g_return_if_fail(json != NULL); g_string_append_c(json->str, '{'); } void s_json_gen_end_object(SJsonGen* json) { g_return_if_fail(json != NULL); strip_comma(json); g_string_append(json->str, "},"); } void s_json_gen_start_array(SJsonGen* json) { g_return_if_fail(json != NULL); g_string_append_c(json->str, '['); } void s_json_gen_end_array(SJsonGen* json) { g_return_if_fail(json != NULL); strip_comma(json); g_string_append(json->str, "],"); } static void escape_string(GString* str, const gchar* v) { g_return_if_fail(str != NULL); g_return_if_fail(v != NULL); const guchar* c = (const guchar*)v; const guchar* m = NULL; const guchar* s; g_string_append_c(str, '"'); while (TRUE) { s = c; #line 1173 "sjson.gen.c" { guchar yych; yych = (guchar)*c; if (yych <= '\v') { if (yych <= 0x08) { if (yych <= 0x00) goto yy103; if (yych <= 0x07) goto yy105; goto yy93; } else { if (yych <= '\t') goto yy95; if (yych >= '\v') goto yy105; } } else { if (yych <= '!') { if (yych <= '\f') goto yy97; if (yych <= '\r') goto yy91; goto yy105; } else { if (yych <= '"') goto yy99; if (yych == '\\') goto yy101; goto yy105; } } ++c; #line 736 "sjson.c" { g_string_append(str, "\\n"); continue; } #line 1200 "sjson.gen.c" yy91: ++c; #line 737 "sjson.c" { g_string_append(str, "\\r"); continue; } #line 1205 "sjson.gen.c" yy93: ++c; #line 738 "sjson.c" { g_string_append(str, "\\b"); continue; } #line 1210 "sjson.gen.c" yy95: ++c; #line 739 "sjson.c" { g_string_append(str, "\\t"); continue; } #line 1215 "sjson.gen.c" yy97: ++c; if ((yych = (guchar)*c) <= '\r') { if (yych <= 0x07) { if (yych >= 0x01) goto yy105; } else { if (yych <= '\n') goto yy98; if (yych <= '\f') goto yy105; } } else { if (yych <= '"') { if (yych <= '!') goto yy105; } else { if (yych != '\\') goto yy105; } } yy98: #line 740 "sjson.c" { g_string_append(str, "\\f"); continue; } #line 1235 "sjson.gen.c" yy99: ++c; #line 741 "sjson.c" { g_string_append(str, "\\\""); continue; } #line 1240 "sjson.gen.c" yy101: ++c; #line 742 "sjson.c" { g_string_append(str, "\\\\"); continue; } #line 1245 "sjson.gen.c" yy103: ++c; #line 744 "sjson.c" { break; } #line 1252 "sjson.gen.c" yy105: ++c; yych = (guchar)*c; if (yych <= '\r') { if (yych <= 0x07) { if (yych >= 0x01) goto yy105; } else { if (yych <= '\n') goto yy107; if (yych <= '\f') goto yy105; } } else { if (yych <= '"') { if (yych <= '!') goto yy105; } else { if (yych != '\\') goto yy105; } } yy107: #line 748 "sjson.c" { g_string_append_len(str, (gchar*)s, c - s); continue; } #line 1276 "sjson.gen.c" } #line 752 "sjson.c" } g_string_append_c(str, '"'); } void s_json_gen_build(SJsonGen* json, const gchar* fmt, ...) { va_list args; gchar* v; g_return_if_fail(json != NULL); g_return_if_fail(fmt != NULL); va_start(args, fmt); v = s_json_buildv(fmt, args); va_end(args); s_json_gen_json(json, v); g_free(v); } void s_json_gen_json(SJsonGen* json, const gchar* v) { g_return_if_fail(json != NULL); if (v) { g_string_append(json->str, v); g_string_append_c(json->str, ','); } else s_json_gen_null(json); } void s_json_gen_string(SJsonGen* json, const gchar* v) { g_return_if_fail(json != NULL); if (v) { escape_string(json->str, v); g_string_append_c(json->str, ','); } else s_json_gen_null(json); } void s_json_gen_int(SJsonGen* json, gint64 v) { g_return_if_fail(json != NULL); g_string_append_printf(json->str, "%" G_GINT64_FORMAT ",", v); } void s_json_gen_double(SJsonGen* json, gdouble v) { g_return_if_fail(json != NULL); g_string_append_printf(json->str, "%lg,", v); } void s_json_gen_bool(SJsonGen* json, gboolean v) { g_return_if_fail(json != NULL); g_string_append(json->str, v ? "true," : "false,"); } void s_json_gen_null(SJsonGen* json) { g_return_if_fail(json != NULL); g_string_append(json->str, "null,"); } void s_json_gen_member_build(SJsonGen* json, const gchar* name, const gchar* fmt, ...) { va_list args; gchar* v; g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); g_return_if_fail(fmt != NULL); va_start(args, fmt); v = s_json_buildv(fmt, args); va_end(args); s_json_gen_member_json(json, name, v); g_free(v); } void s_json_gen_member_json(SJsonGen* json, const gchar* name, const gchar* v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_json(json, v); } void s_json_gen_member_string(SJsonGen* json, const gchar* name, const gchar* v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_string(json, v); } void s_json_gen_member_int(SJsonGen* json, const gchar* name, gint64 v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_int(json, v); } void s_json_gen_member_double(SJsonGen* json, const gchar* name, gdouble v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_double(json, v); } void s_json_gen_member_bool(SJsonGen* json, const gchar* name, gboolean v) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_bool(json, v); } void s_json_gen_member_null(SJsonGen* json, const gchar* name) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_null(json); } void s_json_gen_member_array(SJsonGen* json, const gchar* name) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_start_array(json); } void s_json_gen_member_object(SJsonGen* json, const gchar* name) { g_return_if_fail(json != NULL); g_return_if_fail(name != NULL); s_json_gen_string(json, name); strip_comma(json); g_string_append_c(json->str, ':'); s_json_gen_start_object(json); } gchar* s_json_gen_done(SJsonGen* json) { g_return_val_if_fail(json != NULL, NULL); strip_comma(json); gchar* str = g_string_free(json->str, FALSE); g_slice_free(SJsonGen, json); if (s_json_is_valid(str)) return str; g_free(str); return NULL; } // build // formats: // - % ? format // format: sidbnjSJ gchar* s_json_buildv(const gchar* format, va_list args) { g_return_val_if_fail(format != NULL, NULL); GString* str = g_string_sized_new(strlen(format)); const guchar* c = (const guchar*)format; const guchar* m = NULL; const guchar* s; #define IS_NULLABLE (s[1] == '?') #define FMT_FULL(arg_type, code_format, code_leave) \ gboolean is_null = (IS_NULLABLE ? va_arg(args, gboolean) : FALSE); \ gchar fmt = s[IS_NULLABLE ? 2 : 1]; \ arg_type arg = va_arg(args, arg_type); \ if (is_null) { \ g_string_append(str, "null"); \ } else { \ code_format \ } \ code_leave \ continue; #define FMT(arg_type, code) FMT_FULL(arg_type, code, ) while (TRUE) { s = c; #line 1514 "sjson.gen.c" { guchar yych; unsigned int yyaccept = 0; yych = (guchar)*c; if (yych <= ':') { if (yych <= '"') { if (yych <= '\f') { if (yych <= 0x00) goto yy123; if (yych <= 0x08) goto yy125; if (yych >= '\v') goto yy125; } else { if (yych <= 0x1F) { if (yych >= 0x0E) goto yy125; } else { if (yych <= ' ') goto yy110; if (yych <= '!') goto yy125; goto yy112; } } } else { if (yych <= ',') { if (yych == '%') goto yy122; if (yych <= '+') goto yy125; } else { if (yych <= '/') { if (yych <= '-') goto yy114; goto yy125; } else { if (yych <= '0') goto yy116; if (yych <= '9') goto yy117; } } } } else { if (yych <= 'e') { if (yych <= '\\') { if (yych <= '@') goto yy125; if (yych <= 'Z') goto yy121; if (yych >= '\\') goto yy125; } else { if (yych <= '^') { if (yych >= '^') goto yy125; } else { if (yych == '`') goto yy125; goto yy121; } } } else { if (yych <= 't') { if (yych <= 'm') { if (yych <= 'f') goto yy119; goto yy121; } else { if (yych <= 'n') goto yy120; if (yych <= 's') goto yy121; goto yy118; } } else { if (yych <= '{') { if (yych <= 'z') goto yy121; } else { if (yych != '}') goto yy125; } } } } yy110: yyaccept = 0; yych = (guchar)*(m = ++c); goto yy144; yy111: #line 987 "sjson.c" { g_string_append_len(str, s, c - s); continue; } #line 1591 "sjson.gen.c" yy112: yyaccept = 1; yych = (guchar)*(m = ++c); if (yych >= 0x01) goto yy146; yy113: #line 1034 "sjson.c" { goto err; } #line 1601 "sjson.gen.c" yy114: ++c; if ((yych = (guchar)*c) <= '/') goto yy139; if (yych <= '0') goto yy148; if (yych <= '9') goto yy150; goto yy139; yy115: #line 992 "sjson.c" { g_string_append_c(str, '"'); g_string_append_len(str, s, c - s); g_string_append_c(str, '"'); continue; } #line 1616 "sjson.gen.c" yy116: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych <= 'D') { if (yych == '.') goto yy161; goto yy144; } else { if (yych <= 'E') goto yy163; if (yych == 'e') goto yy163; goto yy144; } yy117: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych <= '0') { if (yych == '.') goto yy161; if (yych <= '/') goto yy144; goto yy181; } else { if (yych <= 'E') { if (yych <= 'D') goto yy144; goto yy163; } else { if (yych == 'e') goto yy163; goto yy144; } } yy118: yych = (guchar)*++c; if (yych == 'r') goto yy158; goto yy139; yy119: yych = (guchar)*++c; if (yych == 'a') goto yy155; goto yy139; yy120: yych = (guchar)*++c; if (yych == 'u') goto yy140; goto yy139; yy121: yych = (guchar)*++c; goto yy139; yy122: yyaccept = 1; yych = (guchar)*(m = ++c); if (yych <= 'b') { if (yych <= 'J') { if (yych == '?') goto yy126; if (yych <= 'I') goto yy113; goto yy134; } else { if (yych == 'S') goto yy136; if (yych <= 'a') goto yy113; goto yy128; } } else { if (yych <= 'i') { if (yych == 'd') goto yy130; if (yych <= 'h') goto yy113; goto yy132; } else { if (yych <= 'j') goto yy134; if (yych == 's') goto yy136; goto yy113; } } yy123: ++c; #line 1030 "sjson.c" { break; } #line 1689 "sjson.gen.c" yy125: yych = (guchar)*++c; goto yy113; yy126: yych = (guchar)*++c; if (yych <= 'c') { if (yych <= 'R') { if (yych == 'J') goto yy134; } else { if (yych <= 'S') goto yy136; if (yych == 'b') goto yy128; } } else { if (yych <= 'i') { if (yych <= 'd') goto yy130; if (yych >= 'i') goto yy132; } else { if (yych <= 'j') goto yy134; if (yych == 's') goto yy136; } } yy127: c = m; if (yyaccept <= 1) { if (yyaccept <= 0) { goto yy111; } else { goto yy113; } } else { goto yy115; } yy128: ++c; #line 1026 "sjson.c" { FMT(gboolean, g_string_append(str, arg ? "true" : "false");) } #line 1728 "sjson.gen.c" yy130: ++c; #line 1022 "sjson.c" { FMT(gdouble, g_string_append_printf(str, "%lg" , arg);) } #line 1735 "sjson.gen.c" yy132: ++c; #line 1018 "sjson.c" { FMT(gint64, g_string_append_printf(str, "%" G_GINT64_FORMAT, arg);) } #line 1742 "sjson.gen.c" yy134: ++c; #line 1005 "sjson.c" { FMT_FULL(gchar*, if (arg) { const gchar* end; if (s_json_is_valid_inner(arg, &end)) g_string_append_len(str, arg, end - arg); else g_string_append(str, "null"); } else { g_string_append(str, "null"); }, if (fmt == 'J') g_free(arg);) } #line 1758 "sjson.gen.c" yy136: ++c; #line 1001 "sjson.c" { FMT_FULL(gchar*, if (arg) escape_string(str, arg); else g_string_append(str, "null");, if (fmt == 'S') g_free(arg);) } #line 1765 "sjson.gen.c" yy138: ++c; yych = (guchar)*c; yy139: if (yych <= '@') { if (yych <= '-') { if (yych <= ',') goto yy115; goto yy138; } else { if (yych <= '/') goto yy115; if (yych <= '9') goto yy138; goto yy115; } } else { if (yych <= '_') { if (yych <= 'Z') goto yy138; if (yych <= '^') goto yy115; goto yy138; } else { if (yych <= '`') goto yy115; if (yych <= 'z') goto yy138; goto yy115; } } yy140: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych == 'l') goto yy141; if (yych <= 'z') goto yy138; goto yy115; } } yy141: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych == 'l') goto yy142; if (yych <= 'z') goto yy138; goto yy115; } } yy142: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '"') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; } else { if (yych == ' ') goto yy143; if (yych <= '!') goto yy111; goto yy145; } } else { if (yych <= '/') { if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; if (yych <= '-') goto yy147; goto yy111; } else { if (yych <= '9') { if (yych <= '0') goto yy148; goto yy150; } else { if (yych <= ':') goto yy143; if (yych <= '@') goto yy111; goto yy138; } } } } else { if (yych <= 'f') { if (yych <= '^') { if (yych == '\\') goto yy111; if (yych >= '^') goto yy111; } else { if (yych == '`') goto yy111; if (yych <= 'e') goto yy138; goto yy153; } } else { if (yych <= 't') { if (yych == 'n') goto yy154; if (yych <= 's') goto yy138; goto yy152; } else { if (yych <= '{') { if (yych <= 'z') goto yy138; } else { if (yych != '}') goto yy111; } } } } yy143: yyaccept = 0; m = ++c; yych = (guchar)*c; yy144: if (yych <= ':') { if (yych <= '!') { if (yych <= '\f') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; goto yy111; } else { if (yych <= '\r') goto yy143; if (yych == ' ') goto yy143; goto yy111; } } else { if (yych <= '-') { if (yych <= '"') goto yy145; if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; goto yy164; } else { if (yych <= '/') goto yy111; if (yych <= '0') goto yy179; if (yych <= '9') goto yy181; goto yy143; } } } else { if (yych <= 'm') { if (yych <= '\\') { if (yych == '[') goto yy143; goto yy111; } else { if (yych <= ']') goto yy143; if (yych == 'f') goto yy170; goto yy111; } } else { if (yych <= 'z') { if (yych <= 'n') goto yy171; if (yych == 't') goto yy169; goto yy111; } else { if (yych == '|') goto yy111; if (yych <= '}') goto yy143; goto yy111; } } } yy145: ++c; yych = (guchar)*c; yy146: if (yych <= '"') { if (yych <= 0x00) goto yy127; if (yych <= '!') goto yy145; goto yy143; } else { if (yych == '\\') goto yy195; goto yy145; } yy147: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '0') goto yy148; if (yych <= '9') goto yy150; goto yy115; } } else { if (yych <= '_') { if (yych <= 'Z') goto yy138; if (yych <= '^') goto yy115; goto yy138; } else { if (yych <= '`') goto yy115; if (yych <= 'z') goto yy138; goto yy115; } } yy148: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'E') { if (yych <= '+') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych <= ' ') { if (yych <= 0x1F) goto yy111; goto yy143; } else { if (yych == '"') goto yy145; goto yy111; } } } else { if (yych <= '0') { if (yych <= '-') { if (yych <= ',') goto yy143; goto yy147; } else { if (yych <= '.') goto yy161; if (yych <= '/') goto yy111; goto yy148; } } else { if (yych <= ':') { if (yych >= ':') goto yy143; } else { if (yych <= '@') goto yy111; if (yych <= 'D') goto yy138; goto yy160; } } } } else { if (yych <= 'e') { if (yych <= ']') { if (yych <= 'Z') goto yy138; if (yych == '\\') goto yy111; goto yy143; } else { if (yych <= '_') { if (yych <= '^') goto yy111; goto yy138; } else { if (yych <= '`') goto yy111; if (yych <= 'd') goto yy138; goto yy160; } } } else { if (yych <= 't') { if (yych <= 'm') { if (yych <= 'f') goto yy153; goto yy138; } else { if (yych <= 'n') goto yy154; if (yych <= 's') goto yy138; goto yy152; } } else { if (yych <= '{') { if (yych <= 'z') goto yy138; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy150: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'E') { if (yych <= '+') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych <= ' ') { if (yych <= 0x1F) goto yy111; goto yy143; } else { if (yych == '"') goto yy145; goto yy111; } } } else { if (yych <= '/') { if (yych <= ',') goto yy143; if (yych <= '-') goto yy147; if (yych <= '.') goto yy161; goto yy111; } else { if (yych <= ':') { if (yych <= '9') goto yy150; goto yy143; } else { if (yych <= '@') goto yy111; if (yych <= 'D') goto yy138; goto yy160; } } } } else { if (yych <= 'e') { if (yych <= ']') { if (yych <= 'Z') goto yy138; if (yych == '\\') goto yy111; goto yy143; } else { if (yych <= '_') { if (yych <= '^') goto yy111; goto yy138; } else { if (yych <= '`') goto yy111; if (yych <= 'd') goto yy138; goto yy160; } } } else { if (yych <= 't') { if (yych <= 'm') { if (yych <= 'f') goto yy153; goto yy138; } else { if (yych <= 'n') goto yy154; if (yych <= 's') goto yy138; } } else { if (yych <= '{') { if (yych <= 'z') goto yy138; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy152: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych == 'r') goto yy158; if (yych <= 'z') goto yy138; goto yy115; } } yy153: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych <= 'a') goto yy155; if (yych <= 'z') goto yy138; goto yy115; } } yy154: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych == 'u') goto yy140; if (yych <= 'z') goto yy138; goto yy115; } } yy155: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych == 'l') goto yy156; if (yych <= 'z') goto yy138; goto yy115; } } yy156: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych == 's') goto yy157; if (yych <= 'z') goto yy138; goto yy115; } } yy157: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych == 'e') goto yy142; if (yych <= 'z') goto yy138; goto yy115; } } yy158: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych == 'u') goto yy159; if (yych <= 'z') goto yy138; goto yy115; } } yy159: ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '/') { if (yych == '-') goto yy138; goto yy115; } else { if (yych <= '9') goto yy138; if (yych <= '@') goto yy115; goto yy138; } } else { if (yych <= '`') { if (yych == '_') goto yy138; goto yy115; } else { if (yych == 'e') goto yy142; if (yych <= 'z') goto yy138; goto yy115; } } yy160: yyaccept = 2; m = ++c; yych = (guchar)*c; if (yych <= '9') { if (yych <= ',') { if (yych == '+') goto yy183; goto yy115; } else { if (yych <= '-') goto yy189; if (yych <= '/') goto yy115; goto yy190; } } else { if (yych <= '^') { if (yych <= '@') goto yy115; if (yych <= 'Z') goto yy138; goto yy115; } else { if (yych == '`') goto yy115; if (yych <= 'z') goto yy138; goto yy115; } } yy161: ++c; yych = (guchar)*c; if (yych <= '/') goto yy127; if (yych >= ':') goto yy127; yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'D') { if (yych <= '"') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych == ' ') goto yy143; if (yych <= '!') goto yy111; goto yy145; } } else { if (yych <= '/') { if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; if (yych <= '-') goto yy164; goto yy111; } else { if (yych <= '0') goto yy165; if (yych <= '9') goto yy167; if (yych <= ':') goto yy143; goto yy111; } } } else { if (yych <= 'f') { if (yych <= '\\') { if (yych <= 'E') goto yy163; if (yych == '[') goto yy143; goto yy111; } else { if (yych <= ']') goto yy143; if (yych <= 'd') goto yy111; if (yych >= 'f') goto yy170; } } else { if (yych <= 't') { if (yych == 'n') goto yy171; if (yych <= 's') goto yy111; goto yy169; } else { if (yych <= '{') { if (yych <= 'z') goto yy111; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy163: ++c; yych = (guchar)*c; if (yych <= ',') { if (yych == '+') goto yy183; goto yy127; } else { if (yych <= '-') goto yy183; if (yych <= '/') goto yy127; if (yych <= '9') goto yy184; goto yy127; } yy164: ++c; yych = (guchar)*c; if (yych <= '/') goto yy127; if (yych <= '0') goto yy179; if (yych <= '9') goto yy181; goto yy127; yy165: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'D') { if (yych <= '"') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych == ' ') goto yy143; if (yych <= '!') goto yy111; goto yy145; } } else { if (yych <= '.') { if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; if (yych <= '-') goto yy164; goto yy161; } else { if (yych <= '0') { if (yych <= '/') goto yy111; goto yy165; } else { if (yych <= '9') goto yy167; if (yych <= ':') goto yy143; goto yy111; } } } } else { if (yych <= 'f') { if (yych <= '\\') { if (yych <= 'E') goto yy163; if (yych == '[') goto yy143; goto yy111; } else { if (yych <= ']') goto yy143; if (yych <= 'd') goto yy111; if (yych <= 'e') goto yy163; goto yy170; } } else { if (yych <= 't') { if (yych == 'n') goto yy171; if (yych <= 's') goto yy111; goto yy169; } else { if (yych <= '{') { if (yych <= 'z') goto yy111; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy167: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'D') { if (yych <= '"') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych == ' ') goto yy143; if (yych <= '!') goto yy111; goto yy145; } } else { if (yych <= '.') { if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; if (yych <= '-') goto yy164; goto yy161; } else { if (yych <= '/') goto yy111; if (yych <= '9') goto yy167; if (yych <= ':') goto yy143; goto yy111; } } } else { if (yych <= 'f') { if (yych <= '\\') { if (yych <= 'E') goto yy163; if (yych == '[') goto yy143; goto yy111; } else { if (yych <= ']') goto yy143; if (yych <= 'd') goto yy111; if (yych <= 'e') goto yy163; goto yy170; } } else { if (yych <= 't') { if (yych == 'n') goto yy171; if (yych <= 's') goto yy111; } else { if (yych <= '{') { if (yych <= 'z') goto yy111; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy169: ++c; yych = (guchar)*c; if (yych == 'r') goto yy177; goto yy127; yy170: ++c; yych = (guchar)*c; if (yych == 'a') goto yy174; goto yy127; yy171: ++c; yych = (guchar)*c; if (yych != 'u') goto yy127; ++c; yych = (guchar)*c; if (yych != 'l') goto yy127; ++c; yych = (guchar)*c; if (yych == 'l') goto yy143; goto yy127; yy174: ++c; yych = (guchar)*c; if (yych != 'l') goto yy127; ++c; yych = (guchar)*c; if (yych != 's') goto yy127; ++c; yych = (guchar)*c; if (yych == 'e') goto yy143; goto yy127; yy177: ++c; yych = (guchar)*c; if (yych != 'u') goto yy127; ++c; yych = (guchar)*c; if (yych == 'e') goto yy143; goto yy127; yy179: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'D') { if (yych <= '"') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych == ' ') goto yy143; if (yych <= '!') goto yy111; goto yy145; } } else { if (yych <= '.') { if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; if (yych <= '-') goto yy164; goto yy161; } else { if (yych <= '0') { if (yych <= '/') goto yy111; goto yy179; } else { if (yych <= '9') goto yy181; if (yych <= ':') goto yy143; goto yy111; } } } } else { if (yych <= 'f') { if (yych <= '\\') { if (yych <= 'E') goto yy163; if (yych == '[') goto yy143; goto yy111; } else { if (yych <= ']') goto yy143; if (yych <= 'd') goto yy111; if (yych <= 'e') goto yy163; goto yy170; } } else { if (yych <= 't') { if (yych == 'n') goto yy171; if (yych <= 's') goto yy111; goto yy169; } else { if (yych <= '{') { if (yych <= 'z') goto yy111; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy181: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'D') { if (yych <= '"') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych == ' ') goto yy143; if (yych <= '!') goto yy111; goto yy145; } } else { if (yych <= '.') { if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; if (yych <= '-') goto yy164; goto yy161; } else { if (yych <= '/') goto yy111; if (yych <= '9') goto yy181; if (yych <= ':') goto yy143; goto yy111; } } } else { if (yych <= 'f') { if (yych <= '\\') { if (yych <= 'E') goto yy163; if (yych == '[') goto yy143; goto yy111; } else { if (yych <= ']') goto yy143; if (yych <= 'd') goto yy111; if (yych <= 'e') goto yy163; goto yy170; } } else { if (yych <= 't') { if (yych == 'n') goto yy171; if (yych <= 's') goto yy111; goto yy169; } else { if (yych <= '{') { if (yych <= 'z') goto yy111; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy183: ++c; yych = (guchar)*c; if (yych <= '/') goto yy127; if (yych >= ':') goto yy127; yy184: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= ':') { if (yych <= '!') { if (yych <= '\f') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; goto yy111; } else { if (yych <= '\r') goto yy143; if (yych == ' ') goto yy143; goto yy111; } } else { if (yych <= '-') { if (yych <= '"') goto yy145; if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; goto yy164; } else { if (yych <= '/') goto yy111; if (yych <= '0') goto yy185; if (yych <= '9') goto yy187; goto yy143; } } } else { if (yych <= 'm') { if (yych <= '\\') { if (yych == '[') goto yy143; goto yy111; } else { if (yych <= ']') goto yy143; if (yych == 'f') goto yy170; goto yy111; } } else { if (yych <= 'z') { if (yych <= 'n') goto yy171; if (yych == 't') goto yy169; goto yy111; } else { if (yych == '|') goto yy111; if (yych <= '}') goto yy143; goto yy111; } } } yy185: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'D') { if (yych <= '"') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych == ' ') goto yy143; if (yych <= '!') goto yy111; goto yy145; } } else { if (yych <= '.') { if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; if (yych <= '-') goto yy164; goto yy161; } else { if (yych <= '0') { if (yych <= '/') goto yy111; goto yy185; } else { if (yych <= '9') goto yy187; if (yych <= ':') goto yy143; goto yy111; } } } } else { if (yych <= 'f') { if (yych <= '\\') { if (yych <= 'E') goto yy163; if (yych == '[') goto yy143; goto yy111; } else { if (yych <= ']') goto yy143; if (yych <= 'd') goto yy111; if (yych <= 'e') goto yy163; goto yy170; } } else { if (yych <= 't') { if (yych == 'n') goto yy171; if (yych <= 's') goto yy111; goto yy169; } else { if (yych <= '{') { if (yych <= 'z') goto yy111; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy187: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'D') { if (yych <= '"') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych == ' ') goto yy143; if (yych <= '!') goto yy111; goto yy145; } } else { if (yych <= '.') { if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; if (yych <= '-') goto yy164; goto yy161; } else { if (yych <= '/') goto yy111; if (yych <= '9') goto yy187; if (yych <= ':') goto yy143; goto yy111; } } } else { if (yych <= 'f') { if (yych <= '\\') { if (yych <= 'E') goto yy163; if (yych == '[') goto yy143; goto yy111; } else { if (yych <= ']') goto yy143; if (yych <= 'd') goto yy111; if (yych <= 'e') goto yy163; goto yy170; } } else { if (yych <= 't') { if (yych == 'n') goto yy171; if (yych <= 's') goto yy111; goto yy169; } else { if (yych <= '{') { if (yych <= 'z') goto yy111; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy189: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '-') { if (yych <= ',') goto yy115; goto yy138; } else { if (yych <= '/') goto yy115; if (yych >= ':') goto yy115; } } else { if (yych <= '_') { if (yych <= 'Z') goto yy138; if (yych <= '^') goto yy115; goto yy138; } else { if (yych <= '`') goto yy115; if (yych <= 'z') goto yy138; goto yy115; } } yy190: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'Z') { if (yych <= '"') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych == ' ') goto yy143; if (yych <= '!') goto yy111; goto yy145; } } else { if (yych <= '/') { if (yych <= '+') goto yy111; if (yych <= ',') goto yy143; if (yych <= '-') goto yy147; goto yy111; } else { if (yych <= '9') { if (yych >= '1') goto yy193; } else { if (yych <= ':') goto yy143; if (yych <= '@') goto yy111; goto yy138; } } } } else { if (yych <= 'f') { if (yych <= '^') { if (yych == '\\') goto yy111; if (yych <= ']') goto yy143; goto yy111; } else { if (yych == '`') goto yy111; if (yych <= 'e') goto yy138; goto yy153; } } else { if (yych <= 't') { if (yych == 'n') goto yy154; if (yych <= 's') goto yy138; goto yy152; } else { if (yych <= '{') { if (yych <= 'z') goto yy138; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy191: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'E') { if (yych <= '+') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych <= ' ') { if (yych <= 0x1F) goto yy111; goto yy143; } else { if (yych == '"') goto yy145; goto yy111; } } } else { if (yych <= '0') { if (yych <= '-') { if (yych <= ',') goto yy143; goto yy147; } else { if (yych <= '.') goto yy161; if (yych <= '/') goto yy111; goto yy191; } } else { if (yych <= ':') { if (yych >= ':') goto yy143; } else { if (yych <= '@') goto yy111; if (yych <= 'D') goto yy138; goto yy160; } } } } else { if (yych <= 'e') { if (yych <= ']') { if (yych <= 'Z') goto yy138; if (yych == '\\') goto yy111; goto yy143; } else { if (yych <= '_') { if (yych <= '^') goto yy111; goto yy138; } else { if (yych <= '`') goto yy111; if (yych <= 'd') goto yy138; goto yy160; } } } else { if (yych <= 't') { if (yych <= 'm') { if (yych <= 'f') goto yy153; goto yy138; } else { if (yych <= 'n') goto yy154; if (yych <= 's') goto yy138; goto yy152; } } else { if (yych <= '{') { if (yych <= 'z') goto yy138; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy193: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= 'E') { if (yych <= '+') { if (yych <= '\r') { if (yych <= 0x08) goto yy111; if (yych <= '\n') goto yy143; if (yych <= '\f') goto yy111; goto yy143; } else { if (yych <= ' ') { if (yych <= 0x1F) goto yy111; goto yy143; } else { if (yych == '"') goto yy145; goto yy111; } } } else { if (yych <= '/') { if (yych <= ',') goto yy143; if (yych <= '-') goto yy147; if (yych <= '.') goto yy161; goto yy111; } else { if (yych <= ':') { if (yych <= '9') goto yy193; goto yy143; } else { if (yych <= '@') goto yy111; if (yych <= 'D') goto yy138; goto yy160; } } } } else { if (yych <= 'e') { if (yych <= ']') { if (yych <= 'Z') goto yy138; if (yych == '\\') goto yy111; goto yy143; } else { if (yych <= '_') { if (yych <= '^') goto yy111; goto yy138; } else { if (yych <= '`') goto yy111; if (yych <= 'd') goto yy138; goto yy160; } } } else { if (yych <= 't') { if (yych <= 'm') { if (yych <= 'f') goto yy153; goto yy138; } else { if (yych <= 'n') goto yy154; if (yych <= 's') goto yy138; goto yy152; } } else { if (yych <= '{') { if (yych <= 'z') goto yy138; goto yy143; } else { if (yych == '}') goto yy143; goto yy111; } } } } yy195: ++c; yych = (guchar)*c; if (yych <= 'e') { if (yych <= '/') { if (yych == '"') goto yy197; if (yych <= '.') goto yy127; goto yy197; } else { if (yych <= '\\') { if (yych <= '[') goto yy127; goto yy197; } else { if (yych == 'b') goto yy197; goto yy127; } } } else { if (yych <= 'q') { if (yych <= 'f') goto yy197; if (yych == 'n') goto yy197; goto yy127; } else { if (yych <= 's') { if (yych <= 'r') goto yy197; goto yy127; } else { if (yych <= 't') goto yy197; if (yych >= 'v') goto yy127; } } } ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy127; if (yych <= '9') goto yy199; goto yy127; } else { if (yych <= 'F') goto yy199; if (yych <= '`') goto yy127; if (yych <= 'f') goto yy199; goto yy127; } yy197: ++c; yych = (guchar)*c; if (yych <= '"') { if (yych <= 0x00) goto yy127; if (yych <= '!') goto yy197; goto yy143; } else { if (yych == '\\') goto yy195; goto yy197; } yy199: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy127; if (yych >= ':') goto yy127; } else { if (yych <= 'F') goto yy200; if (yych <= '`') goto yy127; if (yych >= 'g') goto yy127; } yy200: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy127; if (yych >= ':') goto yy127; } else { if (yych <= 'F') goto yy201; if (yych <= '`') goto yy127; if (yych >= 'g') goto yy127; } yy201: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy127; if (yych <= '9') goto yy197; goto yy127; } else { if (yych <= 'F') goto yy197; if (yych <= '`') goto yy127; if (yych <= 'f') goto yy197; goto yy127; } } #line 1037 "sjson.c" } if (s_json_is_valid(str->str)) return g_string_free(str, FALSE); err: g_string_free(str, TRUE); return NULL; } gchar* s_json_build(const gchar* format, ...) { va_list args; gchar* json; g_return_val_if_fail(format != NULL, NULL); va_start(args, format); json = s_json_buildv(format, args); va_end(args); return json; } gchar* s_json_pretty(const gchar* json) { const gchar* start; const gchar* end; GString *str, *ind; gchar* json_valid; g_return_val_if_fail(json != NULL, NULL); // validate and isolate json_valid = s_json_get(json); if (!json_valid) return NULL; start = json_valid; str = g_string_sized_new(strlen(json) * 2); ind = g_string_sized_new(50); #define PUSH_INDENT() g_string_append_c(ind, '\t') #define POP_INDENT() g_string_truncate(ind, MAX(ind->len - 1, 0)) #define INDENT() g_string_append_len(str, ind->str, ind->len) gint prev_token = TOK_NONE; while (TRUE) { gint token = s_json_get_token(start, &start, &end); if (token == TOK_NONE) break; gint next_token = s_json_get_token(end, NULL, NULL); if ((token == TOK_OBJ_END && prev_token != TOK_OBJ_START) || (token == TOK_ARRAY_END && prev_token != TOK_ARRAY_START && prev_token != TOK_OBJ_END)) { g_string_append_c(str, '\n'); POP_INDENT(); INDENT(); } g_string_append_len(str, start, end - start); if ((token == TOK_OBJ_START && next_token != TOK_OBJ_END) || (token == TOK_ARRAY_START && next_token != TOK_OBJ_START && next_token != TOK_ARRAY_END)) { g_string_append_c(str, '\n'); PUSH_INDENT(); INDENT(); } else if (token == TOK_COMMA) { if ((prev_token == TOK_OBJ_END && next_token == TOK_OBJ_START) || (prev_token == TOK_ARRAY_END && next_token == TOK_ARRAY_START)) { g_string_append_c(str, ' '); } else { g_string_append_c(str, '\n'); INDENT(); } } else if (token == TOK_COLON) { g_string_append_c(str, ' '); } prev_token = token; start = end; } g_string_free(ind, TRUE); g_free(json_valid); return g_string_free(str, FALSE); } const gchar* s_json_path(const gchar* json, const gchar* path) { const guchar* c = (const guchar*)path; const guchar* m = NULL; const guchar* s; const gchar* cur_node = json; g_return_val_if_fail(json != NULL, NULL); g_return_val_if_fail(path != NULL, NULL); #define CHECK_TYPE(type) \ if (cur_node && s_json_get_type(cur_node) == type) \ return cur_node; \ if ((!cur_node || s_json_get_type(cur_node) == S_JSON_TYPE_NULL) && s[0] == '?') \ return "null"; \ return NULL; while (TRUE) { s = c; #line 3298 "sjson.gen.c" { guchar yych; unsigned int yyaccept = 0; yych = (guchar)*c; if (yych <= '!') { if (yych <= '\f') { if (yych <= 0x00) goto yy212; if (yych <= 0x08) goto yy214; if (yych >= '\v') goto yy214; } else { if (yych <= '\r') goto yy204; if (yych <= 0x1F) goto yy214; if (yych >= '!') goto yy211; } } else { if (yych <= '.') { if (yych == '$') goto yy206; if (yych <= '-') goto yy214; goto yy208; } else { if (yych <= '?') { if (yych <= '>') goto yy214; goto yy211; } else { if (yych == '[') goto yy210; goto yy214; } } } yy204: ++c; yych = (guchar)*c; goto yy268; yy205: #line 1158 "sjson.c" { // skip whitespace continue; } #line 3338 "sjson.gen.c" yy206: ++c; #line 1163 "sjson.c" { // root cur_node = json; continue; } #line 3347 "sjson.gen.c" yy208: ++c; if ((yych = (guchar)*c) <= 'Z') { if (yych == '-') goto yy264; if (yych >= 'A') goto yy264; } else { if (yych <= '_') { if (yych >= '_') goto yy264; } else { if (yych <= '`') goto yy209; if (yych <= 'z') goto yy264; } } yy209: #line 1236 "sjson.c" { return NULL; } #line 3366 "sjson.gen.c" yy210: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych <= '/') goto yy209; if (yych <= '0') goto yy259; if (yych <= '9') goto yy260; goto yy209; yy211: yych = (guchar)*++c; switch (yych) { case 'a': goto yy215; case 'b': goto yy219; case 'i': goto yy221; case 'n': goto yy225; case 'o': goto yy217; case 's': goto yy223; default: goto yy209; } yy212: ++c; #line 1232 "sjson.c" { break; } #line 3391 "sjson.gen.c" yy214: yych = (guchar)*++c; goto yy209; yy215: yyaccept = 1; yych = (guchar)*(m = ++c); if (yych == 'r') goto yy255; yy216: #line 1228 "sjson.c" { CHECK_TYPE(S_JSON_TYPE_ARRAY) } #line 3404 "sjson.gen.c" yy217: yyaccept = 2; yych = (guchar)*(m = ++c); if (yych == 'b') goto yy250; yy218: #line 1224 "sjson.c" { CHECK_TYPE(S_JSON_TYPE_OBJECT) } #line 3414 "sjson.gen.c" yy219: yyaccept = 3; yych = (guchar)*(m = ++c); if (yych == 'o') goto yy244; yy220: #line 1220 "sjson.c" { CHECK_TYPE(S_JSON_TYPE_BOOL) } #line 3424 "sjson.gen.c" yy221: yyaccept = 4; yych = (guchar)*(m = ++c); if (yych == 'n') goto yy238; yy222: #line 1203 "sjson.c" { if (cur_node && s_json_get_type(cur_node) == S_JSON_TYPE_NUMBER) { const gchar *int_start, *int_end, *i; g_assert(s_json_get_token(cur_node, &int_start, &int_end) == TOK_NUMBER); for (i = int_start; i < int_end; i++) if (*i > '9' || *i < '0') return NULL; return cur_node; } if ((!cur_node || s_json_get_type(cur_node) == S_JSON_TYPE_NULL) && s[0] == '?') return "null"; return NULL; } #line 3447 "sjson.gen.c" yy223: yyaccept = 5; yych = (guchar)*(m = ++c); if (yych == 't') goto yy233; yy224: #line 1199 "sjson.c" { CHECK_TYPE(S_JSON_TYPE_STRING) } #line 3457 "sjson.gen.c" yy225: yyaccept = 6; yych = (guchar)*(m = ++c); if (yych == 'u') goto yy227; yy226: #line 1195 "sjson.c" { CHECK_TYPE(S_JSON_TYPE_NUMBER) } #line 3467 "sjson.gen.c" yy227: yych = (guchar)*++c; if (yych == 'm') goto yy229; yy228: c = m; if (yyaccept <= 3) { if (yyaccept <= 1) { if (yyaccept <= 0) { goto yy209; } else { goto yy216; } } else { if (yyaccept <= 2) { goto yy218; } else { goto yy220; } } } else { if (yyaccept <= 5) { if (yyaccept <= 4) { goto yy222; } else { goto yy224; } } else { goto yy226; } } yy229: yych = (guchar)*++c; if (yych != 'b') goto yy228; yych = (guchar)*++c; if (yych != 'e') goto yy228; yych = (guchar)*++c; if (yych != 'r') goto yy228; yych = (guchar)*++c; goto yy226; yy233: yych = (guchar)*++c; if (yych != 'r') goto yy228; yych = (guchar)*++c; if (yych != 'i') goto yy228; yych = (guchar)*++c; if (yych != 'n') goto yy228; yych = (guchar)*++c; if (yych != 'g') goto yy228; yych = (guchar)*++c; goto yy224; yy238: yych = (guchar)*++c; if (yych != 't') goto yy228; yych = (guchar)*++c; if (yych != 'e') goto yy228; yych = (guchar)*++c; if (yych != 'g') goto yy228; yych = (guchar)*++c; if (yych != 'e') goto yy228; yych = (guchar)*++c; if (yych != 'r') goto yy228; yych = (guchar)*++c; goto yy222; yy244: yych = (guchar)*++c; if (yych != 'o') goto yy228; yych = (guchar)*++c; if (yych != 'l') goto yy228; yych = (guchar)*++c; if (yych != 'e') goto yy228; yych = (guchar)*++c; if (yych != 'a') goto yy228; yych = (guchar)*++c; if (yych != 'n') goto yy228; yych = (guchar)*++c; goto yy220; yy250: yych = (guchar)*++c; if (yych != 'j') goto yy228; yych = (guchar)*++c; if (yych != 'e') goto yy228; yych = (guchar)*++c; if (yych != 'c') goto yy228; yych = (guchar)*++c; if (yych != 't') goto yy228; yych = (guchar)*++c; goto yy218; yy255: yych = (guchar)*++c; if (yych != 'r') goto yy228; yych = (guchar)*++c; if (yych != 'a') goto yy228; yych = (guchar)*++c; if (yych != 'y') goto yy228; yych = (guchar)*++c; goto yy216; yy259: yych = (guchar)*++c; if (yych == ']') goto yy262; goto yy228; yy260: ++c; yych = (guchar)*c; if (yych <= '/') goto yy228; if (yych <= '9') goto yy260; if (yych != ']') goto yy228; yy262: ++c; #line 1182 "sjson.c" { if (!cur_node || s_json_get_type(cur_node) != S_JSON_TYPE_ARRAY) return NULL; guint index; sscanf(s + 1, "%u", &index); cur_node = s_json_get_element(cur_node, index); continue; } #line 3587 "sjson.gen.c" yy264: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '-') { if (yych >= '-') goto yy264; } else { if (yych <= '/') goto yy266; if (yych <= '9') goto yy264; } } else { if (yych <= '_') { if (yych <= 'Z') goto yy264; if (yych >= '_') goto yy264; } else { if (yych <= '`') goto yy266; if (yych <= 'z') goto yy264; } } yy266: #line 1169 "sjson.c" { if (!cur_node || s_json_get_type(cur_node) != S_JSON_TYPE_OBJECT) return NULL; // zero terminate member name on stack gchar* name = g_alloca((c - s)); memcpy(name, s + 1, c - (s + 1)); name[c - (s + 1)] = '\0'; cur_node = s_json_get_member(cur_node, name); continue; } #line 3621 "sjson.gen.c" yy267: ++c; yych = (guchar)*c; yy268: if (yych <= '\f') { if (yych <= 0x08) goto yy205; if (yych <= '\n') goto yy267; goto yy205; } else { if (yych <= '\r') goto yy267; if (yych == ' ') goto yy267; goto yy205; } } #line 1239 "sjson.c" } return cur_node; } gchar* s_json_compact(const gchar* json) { g_return_val_if_fail(json != NULL, NULL); GString* str = g_string_sized_new(strlen(json)); const guchar* c = (const guchar*)json; const guchar* m = NULL; const guchar* s; while (TRUE) { s = c; #line 3657 "sjson.gen.c" { guchar yych; unsigned int yyaccept = 0; yych = (guchar)*c; if (yych <= '9') { if (yych <= ' ') { if (yych <= '\n') { if (yych <= 0x00) goto yy283; if (yych <= 0x08) goto yy285; goto yy281; } else { if (yych == '\r') goto yy281; if (yych <= 0x1F) goto yy285; goto yy281; } } else { if (yych <= ',') { if (yych == '"') goto yy273; if (yych <= '+') goto yy285; } else { if (yych <= '-') goto yy275; if (yych <= '/') goto yy285; if (yych <= '0') goto yy276; goto yy277; } } } else { if (yych <= 'm') { if (yych <= '\\') { if (yych <= ':') goto yy271; if (yych != '[') goto yy285; } else { if (yych <= ']') goto yy271; if (yych == 'f') goto yy279; goto yy285; } } else { if (yych <= 'z') { if (yych <= 'n') goto yy280; if (yych == 't') goto yy278; goto yy285; } else { if (yych == '|') goto yy285; if (yych >= '~') goto yy285; } } } yy271: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych <= '\\') { if (yych <= '/') { if (yych <= '+') { if (yych == '"') goto yy293; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; } } else { if (yych <= ':') { if (yych <= '0') goto yy296; if (yych <= '9') goto yy298; goto yy291; } else { if (yych == '[') goto yy291; } } } else { if (yych <= 's') { if (yych <= 'f') { if (yych <= ']') goto yy291; if (yych >= 'f') goto yy301; } else { if (yych == 'n') goto yy302; } } else { if (yych <= '{') { if (yych <= 't') goto yy300; if (yych >= '{') goto yy291; } else { if (yych == '}') goto yy291; } } } yy272: #line 1259 "sjson.c" { g_string_append_len(str, s, c - s); continue; } #line 3748 "sjson.gen.c" yy273: yyaccept = 1; yych = (guchar)*(m = ++c); if (yych >= 0x01) goto yy294; yy274: #line 1272 "sjson.c" { goto err; } #line 3758 "sjson.gen.c" yy275: yych = (guchar)*++c; if (yych <= '/') goto yy274; if (yych <= '9') goto yy297; goto yy274; yy276: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych <= '[') { if (yych <= '/') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; if (yych <= '.') goto yy308; goto yy272; } } else { if (yych <= ':') { if (yych <= '0') goto yy296; if (yych <= '9') goto yy298; goto yy291; } else { if (yych == 'E') goto yy309; if (yych <= 'Z') goto yy272; goto yy291; } } } else { if (yych <= 'n') { if (yych <= 'd') { if (yych == ']') goto yy291; goto yy272; } else { if (yych <= 'e') goto yy309; if (yych <= 'f') goto yy301; if (yych <= 'm') goto yy272; goto yy302; } } else { if (yych <= 'z') { if (yych == 't') goto yy300; goto yy272; } else { if (yych == '|') goto yy272; if (yych <= '}') goto yy291; goto yy272; } } } yy277: yyaccept = 0; yych = (guchar)*(m = ++c); if (yych <= '[') { if (yych <= '.') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; goto yy308; } } else { if (yych <= ':') { if (yych <= '/') goto yy272; if (yych <= '9') goto yy298; goto yy291; } else { if (yych == 'E') goto yy309; if (yych <= 'Z') goto yy272; goto yy291; } } } else { if (yych <= 'n') { if (yych <= 'd') { if (yych == ']') goto yy291; goto yy272; } else { if (yych <= 'e') goto yy309; if (yych <= 'f') goto yy301; if (yych <= 'm') goto yy272; goto yy302; } } else { if (yych <= 'z') { if (yych == 't') goto yy300; goto yy272; } else { if (yych == '|') goto yy272; if (yych <= '}') goto yy291; goto yy272; } } } yy278: yyaccept = 1; yych = (guchar)*(m = ++c); if (yych == 'r') goto yy306; goto yy274; yy279: yyaccept = 1; yych = (guchar)*(m = ++c); if (yych == 'a') goto yy303; goto yy274; yy280: yyaccept = 1; yych = (guchar)*(m = ++c); if (yych == 'u') goto yy288; goto yy274; yy281: ++c; yych = (guchar)*c; goto yy287; yy282: #line 1264 "sjson.c" { continue; } #line 3881 "sjson.gen.c" yy283: ++c; #line 1268 "sjson.c" { break; } #line 3888 "sjson.gen.c" yy285: yych = (guchar)*++c; goto yy274; yy286: ++c; yych = (guchar)*c; yy287: if (yych <= '\f') { if (yych <= 0x08) goto yy282; if (yych <= '\n') goto yy286; goto yy282; } else { if (yych <= '\r') goto yy286; if (yych == ' ') goto yy286; goto yy282; } yy288: ++c; yych = (guchar)*c; if (yych == 'l') goto yy290; yy289: c = m; if (yyaccept <= 0) { goto yy272; } else { goto yy274; } yy290: ++c; yych = (guchar)*c; if (yych != 'l') goto yy289; yy291: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= '\\') { if (yych <= '/') { if (yych <= '+') { if (yych != '"') goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; goto yy272; } } else { if (yych <= ':') { if (yych <= '0') goto yy296; if (yych <= '9') goto yy298; goto yy291; } else { if (yych == '[') goto yy291; goto yy272; } } } else { if (yych <= 's') { if (yych <= 'f') { if (yych <= ']') goto yy291; if (yych <= 'e') goto yy272; goto yy301; } else { if (yych == 'n') goto yy302; goto yy272; } } else { if (yych <= '{') { if (yych <= 't') goto yy300; if (yych <= 'z') goto yy272; goto yy291; } else { if (yych == '}') goto yy291; goto yy272; } } } yy293: ++c; yych = (guchar)*c; yy294: if (yych <= '"') { if (yych <= 0x00) goto yy289; if (yych <= '!') goto yy293; goto yy291; } else { if (yych == '\\') goto yy321; goto yy293; } yy295: ++c; yych = (guchar)*c; if (yych <= '/') goto yy289; if (yych <= '0') goto yy296; if (yych <= '9') goto yy298; goto yy289; yy296: yyaccept = 0; m = ++c; yych = (guchar)*c; yy297: if (yych <= '[') { if (yych <= '/') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; if (yych <= '.') goto yy308; goto yy272; } } else { if (yych <= ':') { if (yych <= '0') goto yy296; if (yych >= ':') goto yy291; } else { if (yych == 'E') goto yy309; if (yych <= 'Z') goto yy272; goto yy291; } } } else { if (yych <= 'n') { if (yych <= 'd') { if (yych == ']') goto yy291; goto yy272; } else { if (yych <= 'e') goto yy309; if (yych <= 'f') goto yy301; if (yych <= 'm') goto yy272; goto yy302; } } else { if (yych <= 'z') { if (yych == 't') goto yy300; goto yy272; } else { if (yych == '|') goto yy272; if (yych <= '}') goto yy291; goto yy272; } } } yy298: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= '[') { if (yych <= '.') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; goto yy308; } } else { if (yych <= ':') { if (yych <= '/') goto yy272; if (yych <= '9') goto yy298; goto yy291; } else { if (yych == 'E') goto yy309; if (yych <= 'Z') goto yy272; goto yy291; } } } else { if (yych <= 'n') { if (yych <= 'd') { if (yych == ']') goto yy291; goto yy272; } else { if (yych <= 'e') goto yy309; if (yych <= 'f') goto yy301; if (yych <= 'm') goto yy272; goto yy302; } } else { if (yych <= 'z') { if (yych != 't') goto yy272; } else { if (yych == '|') goto yy272; if (yych <= '}') goto yy291; goto yy272; } } } yy300: ++c; yych = (guchar)*c; if (yych == 'r') goto yy306; goto yy289; yy301: ++c; yych = (guchar)*c; if (yych == 'a') goto yy303; goto yy289; yy302: ++c; yych = (guchar)*c; if (yych == 'u') goto yy288; goto yy289; yy303: ++c; yych = (guchar)*c; if (yych != 'l') goto yy289; ++c; yych = (guchar)*c; if (yych != 's') goto yy289; ++c; yych = (guchar)*c; if (yych == 'e') goto yy291; goto yy289; yy306: ++c; yych = (guchar)*c; if (yych != 'u') goto yy289; ++c; yych = (guchar)*c; if (yych == 'e') goto yy291; goto yy289; yy308: ++c; yych = (guchar)*c; if (yych <= '/') goto yy289; if (yych <= '9') goto yy316; goto yy289; yy309: ++c; yych = (guchar)*c; if (yych <= ',') { if (yych != '+') goto yy289; } else { if (yych <= '-') goto yy310; if (yych <= '/') goto yy289; if (yych <= '9') goto yy311; goto yy289; } yy310: ++c; yych = (guchar)*c; if (yych <= '/') goto yy289; if (yych >= ':') goto yy289; yy311: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= '\\') { if (yych <= '/') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; goto yy272; } } else { if (yych <= ':') { if (yych <= '0') goto yy312; if (yych <= '9') goto yy314; goto yy291; } else { if (yych == '[') goto yy291; goto yy272; } } } else { if (yych <= 's') { if (yych <= 'f') { if (yych <= ']') goto yy291; if (yych <= 'e') goto yy272; goto yy301; } else { if (yych == 'n') goto yy302; goto yy272; } } else { if (yych <= '{') { if (yych <= 't') goto yy300; if (yych <= 'z') goto yy272; goto yy291; } else { if (yych == '}') goto yy291; goto yy272; } } } yy312: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= '[') { if (yych <= '/') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; if (yych <= '.') goto yy308; goto yy272; } } else { if (yych <= ':') { if (yych <= '0') goto yy312; if (yych >= ':') goto yy291; } else { if (yych == 'E') goto yy309; if (yych <= 'Z') goto yy272; goto yy291; } } } else { if (yych <= 'n') { if (yych <= 'd') { if (yych == ']') goto yy291; goto yy272; } else { if (yych <= 'e') goto yy309; if (yych <= 'f') goto yy301; if (yych <= 'm') goto yy272; goto yy302; } } else { if (yych <= 'z') { if (yych == 't') goto yy300; goto yy272; } else { if (yych == '|') goto yy272; if (yych <= '}') goto yy291; goto yy272; } } } yy314: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= '[') { if (yych <= '.') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; goto yy308; } } else { if (yych <= ':') { if (yych <= '/') goto yy272; if (yych <= '9') goto yy314; goto yy291; } else { if (yych == 'E') goto yy309; if (yych <= 'Z') goto yy272; goto yy291; } } } else { if (yych <= 'n') { if (yych <= 'd') { if (yych == ']') goto yy291; goto yy272; } else { if (yych <= 'e') goto yy309; if (yych <= 'f') goto yy301; if (yych <= 'm') goto yy272; goto yy302; } } else { if (yych <= 'z') { if (yych == 't') goto yy300; goto yy272; } else { if (yych == '|') goto yy272; if (yych <= '}') goto yy291; goto yy272; } } } yy316: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= '[') { if (yych <= '/') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; goto yy272; } } else { if (yych <= ':') { if (yych <= '0') goto yy317; if (yych <= '9') goto yy319; goto yy291; } else { if (yych == 'E') goto yy309; if (yych <= 'Z') goto yy272; goto yy291; } } } else { if (yych <= 'n') { if (yych <= 'd') { if (yych == ']') goto yy291; goto yy272; } else { if (yych <= 'e') goto yy309; if (yych <= 'f') goto yy301; if (yych <= 'm') goto yy272; goto yy302; } } else { if (yych <= 'z') { if (yych == 't') goto yy300; goto yy272; } else { if (yych == '|') goto yy272; if (yych <= '}') goto yy291; goto yy272; } } } yy317: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= '[') { if (yych <= '/') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; if (yych <= '.') goto yy308; goto yy272; } } else { if (yych <= ':') { if (yych <= '0') goto yy317; if (yych >= ':') goto yy291; } else { if (yych == 'E') goto yy309; if (yych <= 'Z') goto yy272; goto yy291; } } } else { if (yych <= 'n') { if (yych <= 'd') { if (yych == ']') goto yy291; goto yy272; } else { if (yych <= 'e') goto yy309; if (yych <= 'f') goto yy301; if (yych <= 'm') goto yy272; goto yy302; } } else { if (yych <= 'z') { if (yych == 't') goto yy300; goto yy272; } else { if (yych == '|') goto yy272; if (yych <= '}') goto yy291; goto yy272; } } } yy319: yyaccept = 0; m = ++c; yych = (guchar)*c; if (yych <= '[') { if (yych <= '.') { if (yych <= '+') { if (yych == '"') goto yy293; goto yy272; } else { if (yych <= ',') goto yy291; if (yych <= '-') goto yy295; goto yy308; } } else { if (yych <= ':') { if (yych <= '/') goto yy272; if (yych <= '9') goto yy319; goto yy291; } else { if (yych == 'E') goto yy309; if (yych <= 'Z') goto yy272; goto yy291; } } } else { if (yych <= 'n') { if (yych <= 'd') { if (yych == ']') goto yy291; goto yy272; } else { if (yych <= 'e') goto yy309; if (yych <= 'f') goto yy301; if (yych <= 'm') goto yy272; goto yy302; } } else { if (yych <= 'z') { if (yych == 't') goto yy300; goto yy272; } else { if (yych == '|') goto yy272; if (yych <= '}') goto yy291; goto yy272; } } } yy321: ++c; yych = (guchar)*c; if (yych <= 'e') { if (yych <= '/') { if (yych == '"') goto yy323; if (yych <= '.') goto yy289; goto yy323; } else { if (yych <= '\\') { if (yych <= '[') goto yy289; goto yy323; } else { if (yych == 'b') goto yy323; goto yy289; } } } else { if (yych <= 'q') { if (yych <= 'f') goto yy323; if (yych == 'n') goto yy323; goto yy289; } else { if (yych <= 's') { if (yych <= 'r') goto yy323; goto yy289; } else { if (yych <= 't') goto yy323; if (yych >= 'v') goto yy289; } } } ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy289; if (yych <= '9') goto yy325; goto yy289; } else { if (yych <= 'F') goto yy325; if (yych <= '`') goto yy289; if (yych <= 'f') goto yy325; goto yy289; } yy323: ++c; yych = (guchar)*c; if (yych <= '"') { if (yych <= 0x00) goto yy289; if (yych <= '!') goto yy323; goto yy291; } else { if (yych == '\\') goto yy321; goto yy323; } yy325: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy289; if (yych >= ':') goto yy289; } else { if (yych <= 'F') goto yy326; if (yych <= '`') goto yy289; if (yych >= 'g') goto yy289; } yy326: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy289; if (yych >= ':') goto yy289; } else { if (yych <= 'F') goto yy327; if (yych <= '`') goto yy289; if (yych >= 'g') goto yy289; } yy327: ++c; yych = (guchar)*c; if (yych <= '@') { if (yych <= '/') goto yy289; if (yych <= '9') goto yy323; goto yy289; } else { if (yych <= 'F') goto yy323; if (yych <= '`') goto yy289; if (yych <= 'f') goto yy323; goto yy289; } } #line 1275 "sjson.c" } return g_string_free(str, FALSE); err: g_string_free(str, TRUE); return NULL; } megatools-1.11.3.20250203/lib/sjson.h000066400000000000000000000123651474777720000165640ustar00rootroot00000000000000/** * sjson - fast string based JSON parser/generator library * Copyright (C) 2013 Ondřej Jirman * * WWW: https://github.com/megous/sjson * * This program is free software: you can redistribute it and/or modify * it under the terms of the 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 . */ #ifndef __S_JSON_H__ #define __S_JSON_H__ #include // json value types typedef enum { S_JSON_TYPE_NONE = 0, S_JSON_TYPE_OBJECT, S_JSON_TYPE_ARRAY, S_JSON_TYPE_STRING, S_JSON_TYPE_NUMBER, S_JSON_TYPE_BOOL, S_JSON_TYPE_NULL, S_JSON_TYPE_INVALID } SJsonType; typedef struct _SJsonGen SJsonGen; // parser gboolean s_json_is_valid (const gchar* json); gchar* s_json_get (const gchar* json); SJsonType s_json_get_type (const gchar* json); const gchar* s_json_get_element_first (const gchar* json); const gchar* s_json_get_element_next (const gchar* iter); const gchar* s_json_get_element (const gchar* json, guint index); gchar** s_json_get_elements (const gchar* json); const gchar* s_json_get_member_first (const gchar* json, const gchar** value); const gchar* s_json_get_member_next (const gchar** value); const gchar* s_json_get_member (const gchar* json, const gchar* name); gchar* s_json_get_string (const gchar* json); gint64 s_json_get_int (const gchar* json, gint64 fallback); gdouble s_json_get_double (const gchar* json, gdouble fallback); gboolean s_json_get_bool (const gchar* json); gboolean s_json_is_null (const gchar* json); gchar* s_json_get_member_string (const gchar* json, const gchar* name); gint64 s_json_get_member_int (const gchar* json, const gchar* name, gint64 fallback); gdouble s_json_get_member_double (const gchar* json, const gchar* name, gdouble fallback); gboolean s_json_get_member_bool (const gchar* json, const gchar* name); gboolean s_json_member_is_null (const gchar* json, const gchar* name); // helper utils gboolean s_json_string_match (const gchar* json_str, const gchar* c_str); // json path const gchar* s_json_path (const gchar* json, const gchar* path); // generator SJsonGen* s_json_gen_new (void); void s_json_gen_start_object (SJsonGen* json); void s_json_gen_end_object (SJsonGen* json); void s_json_gen_start_array (SJsonGen* json); void s_json_gen_end_array (SJsonGen* json); void s_json_gen_json (SJsonGen* json, const gchar* v); void s_json_gen_build (SJsonGen* json, const gchar* fmt, ...); void s_json_gen_string (SJsonGen* json, const gchar* v); void s_json_gen_int (SJsonGen* json, gint64 v); void s_json_gen_double (SJsonGen* json, gdouble v); void s_json_gen_bool (SJsonGen* json, gboolean v); void s_json_gen_null (SJsonGen* json); void s_json_gen_member_json (SJsonGen* json, const gchar* name, const gchar* v); void s_json_gen_member_build (SJsonGen* json, const gchar* name, const gchar* fmt, ...); void s_json_gen_member_string (SJsonGen* json, const gchar* name, const gchar* v); void s_json_gen_member_int (SJsonGen* json, const gchar* name, gint64 v); void s_json_gen_member_double (SJsonGen* json, const gchar* name, gdouble v); void s_json_gen_member_bool (SJsonGen* json, const gchar* name, gboolean v); void s_json_gen_member_null (SJsonGen* json, const gchar* name); void s_json_gen_member_array (SJsonGen* json, const gchar* name); void s_json_gen_member_object (SJsonGen* json, const gchar* name); gchar* s_json_gen_done (SJsonGen* json); // builder gchar* s_json_buildv (const gchar* format, va_list args); gchar* s_json_build (const gchar* format, ...); // formatters gchar* s_json_pretty (const gchar* json); gchar* s_json_compact (const gchar* json); // iterator macros #define S_JSON_FOREACH_ELEMENT(json, iter) \ G_STMT_START { \ const gchar* iter; \ for (iter = s_json_get_element_first(json); iter; iter = s_json_get_element_next(iter)) { #define S_JSON_FOREACH_MEMBER(json, key, value) \ G_STMT_START { \ const gchar *key, *value; \ for (key = s_json_get_member_first(json, &value); key; key = s_json_get_member_next(&value)) { #define S_JSON_FOREACH_END() \ }} G_STMT_END; #endif megatools-1.11.3.20250203/lib/tools.c000066400000000000000000000433611474777720000165630ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include "config.h" #include "tools.h" #include "http.h" #ifdef G_OS_WIN32 #include #else #include #include #include #endif #ifdef G_OS_WIN32 #define MEGA_RC_FILENAME "mega.ini" #else #define MEGA_RC_FILENAME ".megarc" #endif #define BOOLEAN_UNSET_BUT_TRUE 2 static GOptionContext *opt_context; static gchar *opt_username; static gchar *opt_password; static gchar *opt_config; static gboolean opt_reload_files; static gboolean opt_version; static gboolean opt_no_config; static gboolean opt_no_ask_password; static gint opt_speed_limit = -1; /* -1 means limit not set */ static gchar *opt_proxy; static gchar *proxy; static gint upload_speed_limit; static gint download_seed_limit; static gint transfer_worker_count = 5; static gint cache_timout = 10 * 60; static gboolean opt_enable_previews = BOOLEAN_UNSET_BUT_TRUE; static gboolean opt_disable_resume; static gchar *opt_netif; static gchar *opt_ipproto; static gboolean tool_use_colors = FALSE; static gboolean opt_debug_callback(const gchar *option_name, const gchar *value, gpointer data, GError **error) { if (value) { gchar **opts = g_strsplit(value, ",", 0); gchar **opt = opts; while (*opt) { if (g_ascii_strcasecmp(*opt, "api") == 0) mega_debug |= MEGA_DEBUG_API; else if (g_ascii_strcasecmp(*opt, "fs") == 0) mega_debug |= MEGA_DEBUG_FS; else if (g_ascii_strcasecmp(*opt, "cache") == 0) mega_debug |= MEGA_DEBUG_CACHE; else if (g_ascii_strcasecmp(*opt, "http") == 0) mega_debug |= MEGA_DEBUG_HTTP; else if (g_ascii_strcasecmp(*opt, "tman") == 0) mega_debug |= MEGA_DEBUG_TMAN; opt++; } g_strfreev(opts); } else { mega_debug = MEGA_DEBUG_API; } return TRUE; } static GOptionEntry basic_options[] = { { "config", '\0', 0, G_OPTION_ARG_FILENAME, &opt_config, "Load configuration from a file", "PATH" }, { "ignore-config-file", '\0', 0, G_OPTION_ARG_NONE, &opt_no_config, "Disable loading " MEGA_RC_FILENAME, NULL }, { "debug", '\0', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, opt_debug_callback, "Enable debugging output", "OPTS" }, { "version", '\0', 0, G_OPTION_ARG_NONE, &opt_version, "Show version information", NULL }, { NULL } }; static GOptionEntry upload_options[] = { { "enable-previews", '\0', 0, G_OPTION_ARG_NONE, &opt_enable_previews, "Generate previews when uploading file", NULL }, { "disable-previews", '\0', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_enable_previews, "Never generate previews when uploading file", NULL }, { NULL } }; static GOptionEntry download_options[] = { { "disable-resume", '\0', 0, G_OPTION_ARG_NONE, &opt_disable_resume, "Disable resume when downloading file", NULL }, { NULL } }; static GOptionEntry auth_options[] = { { "username", 'u', 0, G_OPTION_ARG_STRING, &opt_username, "Account username (email)", "USERNAME" }, { "password", 'p', 0, G_OPTION_ARG_STRING, &opt_password, "Account password", "PASSWORD" }, { "no-ask-password", '\0', 0, G_OPTION_ARG_NONE, &opt_no_ask_password, "Never ask interactively for a password", NULL }, { "reload", '\0', 0, G_OPTION_ARG_NONE, &opt_reload_files, "Reload filesystem cache", NULL }, { NULL } }; static GOptionEntry network_options[] = { { "limit-speed", '\0', 0, G_OPTION_ARG_INT, &opt_speed_limit, "Limit transfer speed (KiB/s)", "SPEED" }, { "proxy", '\0', 0, G_OPTION_ARG_STRING, &opt_proxy, "Proxy setup string", "PROXY" }, { "netif", '\0', 0, G_OPTION_ARG_STRING, &opt_netif, "Network interface or local IP address used for outgoing connections", "NAME" }, { "ip-proto", '\0', 0, G_OPTION_ARG_STRING, &opt_ipproto, "Which protocol to prefer when connecting to mega.nz (v4, v6, or any)", "PROTO" }, { NULL } }; #if OPENSSL_VERSION_NUMBER >= 0x10100004L static void init_openssl_locking() { // OpenSSL >= 1.1.0-pre4 doesn't require specific callback setup } #else static GMutex *openssl_mutexes = NULL; static void openssl_locking_callback(int mode, int type, const char *file, int line) { if (mode & CRYPTO_LOCK) g_mutex_lock(openssl_mutexes + type); else g_mutex_unlock(openssl_mutexes + type); } static unsigned long openssl_thread_id_callback() { unsigned long ret; ret = (unsigned long)g_thread_self(); return ret; } static void init_openssl_locking() { gint i; // initialize OpenSSL locking for multi-threaded operation openssl_mutexes = g_new(GMutex, CRYPTO_num_locks()); for (i = 0; i < CRYPTO_num_locks(); i++) g_mutex_init(openssl_mutexes + i); SSL_library_init(); CRYPTO_set_id_callback(openssl_thread_id_callback); CRYPTO_set_locking_callback(openssl_locking_callback); } #endif static void init(void) { g_setenv("GSETTINGS_BACKEND", "memory", TRUE); #ifndef G_OS_WIN32 signal(SIGPIPE, SIG_IGN); #endif init_openssl_locking(); } gboolean tool_is_stdout_tty(void) { #ifdef G_OS_WIN32 return FALSE; #else return isatty(1); #endif } #define PROGRESS_FREQUENCY ((gint64)1000000) void tool_show_progress(const gchar *file, const struct mega_status_data *data) { static gint64 last_update = 0; static guint64 last_bytes = 0; static gint64 transfer_start = -1; gint64 timenow = g_get_monotonic_time(); gint64 now_done = 0; gc_free gchar *done_str = NULL; gc_free gchar *total_str = NULL; gc_free gchar *rate_str = NULL; if (data->progress.total == 0) return; // start of the new transfer, initialize progress reporting if (data->progress.done == -1) { transfer_start = last_update = timenow; last_bytes = 0; } else if (transfer_start < 0) return; else if (data->progress.done == -2) now_done = data->progress.total; else if (last_update && last_update + PROGRESS_FREQUENCY > timenow) return; else now_done = data->progress.done; gint64 time_span = timenow - last_update; gint64 size_diff = now_done - last_bytes; gdouble rate, percentage; if (data->progress.done == -2) { // final summary rate = (gdouble)data->progress.total * 1e6 / (timenow - transfer_start); percentage = 100; transfer_start = -1; total_str = g_format_size_full(data->progress.total, G_FORMAT_SIZE_IEC_UNITS); rate_str = g_format_size_full(rate, G_FORMAT_SIZE_IEC_UNITS); if (tool_use_colors) { g_print(ESC_WHITE "%s" ESC_NORMAL ": " ESC_YELLOW "%.2f%%" ESC_NORMAL " - " "done " ESC_GREEN "%s" ESC_NORMAL " (avg. %s/s)\n", file, percentage, total_str, rate_str); } else { g_print("%s: %.2f%% - done %s (avg. %s/s)\n", file, percentage, total_str, rate_str); } } else if (time_span == 0) { // just started percentage = 0; done_str = g_format_size_full(now_done, G_FORMAT_SIZE_IEC_UNITS | G_FORMAT_SIZE_LONG_FORMAT); total_str = g_format_size_full(data->progress.total, G_FORMAT_SIZE_IEC_UNITS); if (tool_use_colors) { g_print(ESC_WHITE "%s" ESC_NORMAL ": " ESC_YELLOW "%.2f%%" ESC_NORMAL " - " ESC_GREEN "%s" ESC_BLUE " of %s" ESC_NORMAL, file, percentage, done_str, total_str); } else { g_print("%s: %.2f%% - %s of %s", file, percentage, done_str, total_str); } g_print("%s", tool_is_stdout_tty() ? ESC_CLREOL "\r" : "\n"); } else { // regular update rate = (gdouble)size_diff * 1e6 / time_span; percentage = (gdouble)now_done / data->progress.total * 100; done_str = g_format_size_full(now_done, G_FORMAT_SIZE_IEC_UNITS | G_FORMAT_SIZE_LONG_FORMAT); total_str = g_format_size_full(data->progress.total, G_FORMAT_SIZE_IEC_UNITS); rate_str = g_format_size_full(rate, G_FORMAT_SIZE_IEC_UNITS); if (tool_use_colors) { g_print(ESC_WHITE "%s" ESC_NORMAL ": " ESC_YELLOW "%.2f%%" ESC_NORMAL " - " ESC_GREEN "%s" ESC_BLUE " of %s" ESC_NORMAL " (%s/s)", file, percentage, done_str, total_str, rate_str); } else { g_print("%s: %.2f%% - %s of %s (%s/s)", file, percentage, done_str, total_str, rate_str); } g_print("%s", tool_is_stdout_tty() ? ESC_CLREOL "\r" : "\n"); } last_update = timenow; last_bytes = now_done; } gchar* tool_prompt_input(void) { gc_string_free GString* str = g_string_sized_new(1024); gchar* ret = NULL; while (TRUE) { char buf[256]; if (fgets(buf, sizeof buf, stdin) == NULL) return NULL; int len = strlen(buf); gboolean has_eol = buf[len - 1] == '\n'; if (len > 0) g_string_append_len(str, buf, has_eol ? len - 1 : len); if (has_eol || feof(stdin)) break; } ret = g_string_free(str, FALSE); str = NULL; return ret; } static gchar *input_password(void) { gint tries = 3; gchar buf[1025]; gchar *password = NULL; #ifdef G_OS_WIN32 HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); DWORD mode = 0; GetConsoleMode(hStdin, &mode); SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT)); #else struct termios oldt; tcgetattr(STDIN_FILENO, &oldt); struct termios newt = oldt; newt.c_lflag &= ~ECHO; tcsetattr(STDIN_FILENO, TCSANOW, &newt); #endif again: g_print("Enter password for (%s): ", opt_username); if (fgets(buf, 1024, stdin)) { if (strlen(buf) > 1) { password = g_strndup(buf, strcspn(buf, "\r\n")); } else { if (--tries > 0) { g_print("\n"); goto again; } g_print("\nYou need to provide non-empty password!\n"); exit(1); } } else { g_printerr("\nERROR: Can't read password from the input!\n"); exit(1); } #ifdef G_OS_WIN32 SetConsoleMode(hStdin, mode); #else tcsetattr(STDIN_FILENO, TCSANOW, &oldt); #endif g_print("\nGood, signing in...\n"); return password; } static void print_version(void) { if (opt_version) { g_print("megatools " VERSION " - command line tools for Mega.nz\n\n"); g_print("Written by Ondrej Jirman , 2013-2025\n"); g_print("Go to https://xff.cz/megatools/ for more information\n"); exit(0); } } void tool_init(gint *ac, gchar ***av, const gchar *tool_name, GOptionEntry *tool_entries, ToolInitFlags flags) { GError *local_err = NULL; gchar** args = NULL; init(); opt_context = g_option_context_new(tool_name); if (tool_entries) g_option_context_add_main_entries(opt_context, tool_entries, NULL); if (flags & TOOL_INIT_UPLOAD_OPTS) g_option_context_add_main_entries(opt_context, upload_options, NULL); if (flags & TOOL_INIT_DOWNLOAD_OPTS) g_option_context_add_main_entries(opt_context, download_options, NULL); if (flags & (TOOL_INIT_AUTH | TOOL_INIT_AUTH_OPTIONAL)) g_option_context_add_main_entries(opt_context, auth_options, NULL); g_option_context_add_main_entries(opt_context, network_options, NULL); g_option_context_add_main_entries(opt_context, basic_options, NULL); if (!g_option_context_parse_strv(opt_context, av, &local_err)) { g_printerr("ERROR: Option parsing failed: %s\n", local_err->message); g_clear_error(&local_err); exit(1); } *ac = g_strv_length(*av); print_version(); if (!opt_no_config || opt_config) { gboolean status = TRUE; gc_key_file_unref GKeyFile *kf = g_key_file_new(); if (opt_config) { if (!g_key_file_load_from_file(kf, opt_config, 0, &local_err)) { g_printerr("ERROR: Failed to open config file: %s: %s\n", opt_config, local_err->message); g_clear_error(&local_err); exit(1); } } else { status = g_key_file_load_from_file(kf, MEGA_RC_FILENAME, 0, NULL); if (!status) { gc_free gchar *tmp = g_build_filename(g_get_home_dir(), MEGA_RC_FILENAME, NULL); status = g_key_file_load_from_file(kf, tmp, 0, NULL); } } if (status) { // load username/password from ini file if (!opt_username) opt_username = g_key_file_get_string(kf, "Login", "Username", NULL); if (!opt_password) opt_password = g_key_file_get_string(kf, "Login", "Password", NULL); gint to = g_key_file_get_integer(kf, "Cache", "Timeout", &local_err); if (local_err == NULL) cache_timout = to; else g_clear_error(&local_err); // Load speed limits from settings file if (g_key_file_has_key(kf, "Network", "SpeedLimit", NULL)) { download_seed_limit = upload_speed_limit = g_key_file_get_integer(kf, "Network", "SpeedLimit", &local_err); if (local_err) { g_printerr("WARNING: Invalid speed limit set in the config file: %s\n", local_err->message); g_clear_error(&local_err); } } if (g_key_file_has_key(kf, "Network", "UploadSpeedLimit", NULL)) { upload_speed_limit = g_key_file_get_integer(kf, "Network", "UploadSpeedLimit", &local_err); if (local_err) { g_printerr("WARNING: Invalid upload speed limit set in the config file: %s\n", local_err->message); g_clear_error(&local_err); } } if (g_key_file_has_key(kf, "Network", "DownloadSpeedLimit", NULL)) { download_seed_limit = g_key_file_get_integer(kf, "Network", "DownloadSpeedLimit", &local_err); if (local_err) { g_printerr("WARNING: Invalid download speed limit set in the config file: %s\n", local_err->message); g_clear_error(&local_err); } } if (g_key_file_has_key(kf, "Network", "ParallelTransfers", NULL)) { transfer_worker_count = g_key_file_get_integer(kf, "Network", "ParallelTransfers", &local_err); if (local_err) { g_printerr( "WARNING: Invalid number of parallel transfers set in the config file: %s\n", local_err->message); g_clear_error(&local_err); } if (transfer_worker_count < 1 || transfer_worker_count > 16) { transfer_worker_count = CLAMP(transfer_worker_count, 1, 16); g_printerr( "WARNING: Invalid number of parallel transfers set in the config file, limited to %d\n", transfer_worker_count); } } proxy = g_key_file_get_string(kf, "Network", "Proxy", NULL); if (opt_enable_previews == BOOLEAN_UNSET_BUT_TRUE) { gboolean enable = g_key_file_get_boolean(kf, "Upload", "CreatePreviews", &local_err); if (local_err == NULL) opt_enable_previews = enable; else g_clear_error(&local_err); } if (g_key_file_has_key(kf, "UI", "Colors", NULL) && tool_is_stdout_tty()) { tool_use_colors = g_key_file_get_boolean(kf, "UI", "Colors", &local_err); if (local_err) { g_printerr( "WARNING: Invalid value for UI.Colors set in the config file: %s\n", local_err->message); g_clear_error(&local_err); tool_use_colors = FALSE; } } } } if (opt_speed_limit >= 0) { upload_speed_limit = opt_speed_limit; download_seed_limit = opt_speed_limit; } if (opt_proxy) { if (!strcmp(opt_proxy, "none")) proxy = NULL; else proxy = opt_proxy; } if (opt_netif) { http_netif = opt_netif; } if (opt_ipproto) { if (!strcmp(opt_ipproto, "v4")) http_ipproto = HTTP_IPPROTO_V4; else if (!strcmp(opt_ipproto, "v6")) http_ipproto = HTTP_IPPROTO_V6; else if (!strcmp(opt_ipproto, "any")) http_ipproto = HTTP_IPPROTO_ANY; else { g_printerr("ERROR: Invalid --ip-proto option.\n"); exit(1); } } if (!(flags & TOOL_INIT_AUTH)) return; if (!opt_username) { g_printerr("ERROR: You must specify your mega.nz username (email)\n"); exit(1); } if (!opt_password && opt_no_ask_password) { g_printerr("ERROR: You must specify your mega.nz password\n"); exit(1); } if (!opt_password) opt_password = input_password(); } struct mega_session *tool_start_session(ToolSessionFlags flags) { GError *local_err = NULL; gboolean is_new_session; struct mega_session *s = mega_session_new(); mega_session_set_speed(s, upload_speed_limit, download_seed_limit); mega_session_set_workers(s, transfer_worker_count); if (proxy) mega_session_set_proxy(s, proxy); mega_session_enable_previews(s, TRUE); if (!(flags & TOOL_SESSION_OPEN)) return s; // allow unatuhenticated session if (!opt_password || !opt_username) { if (flags & TOOL_SESSION_AUTH_OPTIONAL) return s; g_printerr("ERROR: Authentication is required\n"); goto err; } // open the session if (!mega_session_open(s, opt_username, opt_password, cache_timout, &is_new_session, &local_err)) { g_printerr("ERROR: Can't login to mega.nz: %s\n", local_err->message); goto err; } if (is_new_session) mega_session_save(s, NULL); if (!(flags & TOOL_SESSION_AUTH_ONLY) && (opt_reload_files || is_new_session)) { if (!mega_session_refresh(s, &local_err)) { g_printerr("ERROR: Can't read filesystem info from mega.nz: %s\n", local_err->message); goto err; } mega_session_save(s, NULL); } mega_session_enable_previews(s, !!opt_enable_previews); mega_session_set_resume(s, !opt_disable_resume); return s; err: mega_session_free(s); g_clear_error(&local_err); return NULL; } void tool_fini(struct mega_session *s) { if (s) mega_session_free(s); mega_cleanup(); g_option_context_free(opt_context); curl_global_cleanup(); CRYPTO_cleanup_all_ex_data(); ERR_free_strings(); #if OPENSSL_VERSION_NUMBER < 0x10100004L CRYPTO_set_id_callback(NULL); CRYPTO_set_locking_callback(NULL); #endif } megatools-1.11.3.20250203/lib/tools.h000066400000000000000000000040551474777720000165650ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __TOOLS_H #define __TOOLS_H #include #include #include #include #include #include "mega.h" #include "alloc.h" typedef enum { TOOL_SESSION_OPEN = 1, TOOL_SESSION_AUTH_ONLY = 2, TOOL_SESSION_AUTH_OPTIONAL = 4, } ToolSessionFlags; typedef enum { TOOL_INIT_AUTH = 1, // accept auth options, require them TOOL_INIT_AUTH_OPTIONAL = 2, // accept auth options, optionally TOOL_INIT_UPLOAD_OPTS = 4, // accept upload options TOOL_INIT_DOWNLOAD_OPTS = 8, // accept download options } ToolInitFlags; void tool_init(gint *ac, gchar ***av, const gchar *tool_name, GOptionEntry *tool_entries, ToolInitFlags flags); struct mega_session *tool_start_session(ToolSessionFlags flags); void tool_fini(struct mega_session *s); void tool_show_progress(const gchar *file, const struct mega_status_data *data); gboolean tool_is_stdout_tty(void); gchar* tool_prompt_input(void); #define ESC_CLREOL "\x1b[0K" #define ESC_WHITE "\x1b[37;1m" #define ESC_GREEN "\x1b[32;1m" #define ESC_YELLOW "\x1b[33;1m" #define ESC_BLUE "\x1b[34;1m" #define ESC_GRAY "\x1b[30;1m" #define ESC_NORMAL "\x1b[0m" DEFINE_CLEANUP_FUNCTION(struct mega_session*, tool_fini) #define gc_tool_fini CLEANUP(tool_fini) #endif megatools-1.11.3.20250203/meson.build000066400000000000000000000055161474777720000166530ustar00rootroot00000000000000project('megatools', 'c', version: '1.11.3', default_options: [ 'c_std=gnu99', ], meson_version : '>= 0.46.0', license: 'GPLv3') cc = meson.get_compiler('c') add_global_arguments('-Wno-unused', '-Wno-pointer-sign', language: 'c') deps = [ dependency('threads'), dependency('gio-2.0', version: '>=2.40.0'), dependency('libcurl'), dependency('openssl'), ] #if host_machine.system() == 'windows' # deps += [cc.find_library('wsock32')] #endif # targets cdata = configuration_data() cdata.set_quoted('VERSION', meson.project_version()) cfile = configure_file(configuration: cdata, output: 'config.h') commands = ['df', 'dl', 'get', 'ls', 'test', 'export', 'mkdir', 'put', 'reg', 'rm', 'copy'] executable('megatools', 'lib/sjson.gen.c', 'lib/http.c', 'lib/mega.c', 'lib/tools.c', 'tools/df.c', 'tools/dl.c', 'tools/get.c', 'tools/ls.c', 'tools/test.c', 'tools/export.c', 'tools/mkdir.c', 'tools/put.c', 'tools/reg.c', 'tools/rm.c', 'tools/copy.c', 'tools/shell.c', dependencies: deps, include_directories: include_directories('lib', 'tools', '.'), install: true ) #XXX: contrib/bash-completion/megatools if get_option('symlinks') or true meson.add_install_script('install-symlinks.sh', get_option('bindir')) endif # docs install_data(['README', 'NEWS', 'TODO', 'LICENSE'], install_dir: join_paths(get_option('datadir'), 'doc', meson.project_name()), ) manpages = [ ['megatools', '1'], ['megarc', '5'], ] foreach cmd: commands manpages += [['megatools-' + cmd, '1']] endforeach # build docs asciidoc = find_program('asciidoc', required: false) db2x_xsltproc = find_program('db2x_xsltproc', required: false) db2x_manxml = find_program('db2x_manxml', required: false) enable_manpages = asciidoc.found() and db2x_xsltproc.found() and db2x_manxml.found() if enable_manpages and get_option('man') foreach mp: manpages mandirn = join_paths(get_option('mandir'), 'man' + mp[1]) docb = custom_target(mp[0] + '-db', command: [asciidoc, '-f', files('docs/asciidoc.conf'), '-o', '@OUTPUT@', '-b', 'docbook', '-d', 'manpage', '@INPUT@'], output: [mp[0] + '.xml'], input: ['docs/' + mp[0] + '.txt'], depend_files: [ 'docs/asciidoc.conf', 'docs/footer.txt', 'docs/auth-options.txt', 'docs/basic-options.txt', 'docs/download-options.txt', 'docs/network-options.txt', 'docs/upload-options.txt', 'docs/remote-paths.txt', ], ) mxml = custom_target(mp[0] + '-mxml', command: [db2x_xsltproc, '-s', 'man', '--param', 'xref-on-link=0', '@INPUT@', '-o', '@OUTPUT@'], output: [mp[0] + '.mxml'], input: [docb], ) manx = custom_target(mp[0] + '-man', command: [db2x_manxml, '--to-stdout', '@INPUT@'], capture: true, output: [mp[0] + '.' + mp[1]], input: [mxml], install: true, install_dir: mandirn, ) endforeach endifmegatools-1.11.3.20250203/meson_options.txt000066400000000000000000000004141474777720000201360ustar00rootroot00000000000000option('symlinks', type: 'boolean', value: false, description: 'Install symlinks for compatibility with old megatools commands (megals, megacopy, megadl, ...)' ) option('man', type: 'boolean', value: true, description: 'Build and install manual pages.' ) megatools-1.11.3.20250203/tests/000077500000000000000000000000001474777720000156445ustar00rootroot00000000000000megatools-1.11.3.20250203/tests/download-test/000077500000000000000000000000001474777720000204305ustar00rootroot00000000000000megatools-1.11.3.20250203/tests/download-test/resume-test.sh000077500000000000000000000032331474777720000232450ustar00rootroot00000000000000#!/bin/sh # use build binaries export PATH="../..:$PATH" set -e make -C ../.. err() { echo "FAIL: $@" exit 1 } test_download() { rm -f .tmp/test.dst megaget --path .tmp/test.dst /Root/.testfile || err "download failed: $1" cmp .tmp/test.src .tmp/test.dst || err "source != target post download: $1" echo "OK: $1" } prep_resume_file() # size { cp .tmp/test.src ".tmp/.megatmp.$handle" truncate -s "$1" ".tmp/.megatmp.$handle" } del_resume_file() { rm -f ".tmp/.megatmp.$handle" } prep_source_file() { rm -rf .tmp mkdir -p .tmp dd if=/dev/urandom of=.tmp/test.src bs=1M count=66 truncate -s "$1" .tmp/test.src megarm /Root/.testfile || true megaput --path /Root/.testfile .tmp/test.src handle=`megals -l /Root/.testfile | cut -d ' ' -f 1` egrep -e '^[a-zA-Z0-9]+$' <<< "$handle" || err "wut?" } i=0 while test $i -lt 6 ; do # create source file and upload it (make sure it is big and random sized) # make sure, we test 16byte alignment at least once test_size=$((1024*1024*64)) test $i -eq 0 || test_size=$(($test_size + RANDOM)) prep_source_file $test_size # download file without resume del_resume_file test_download "no resume, test_size=$test_size" prep_resume_file 0 test_download "resume=0, test_size=$test_size" prep_resume_file $test_size test_download "resume=test_size, test_size=$test_size" # download file with varying resume offsets j=0 while test $j -lt 7 ; do resume_size=$(($test_size - RANDOM - (RANDOM % 32 * 1024 * 1024))) test $j -eq 0 && resume_size=$(($resume_size - $resume_size % 16)) prep_resume_file $resume_size test_download "resume=$resume_size, test_size=$test_size" j=$(($j+1)) done i=$(($i+1)) done megatools-1.11.3.20250203/tests/get-pin-key.sh000077500000000000000000000006521474777720000203370ustar00rootroot00000000000000#!/bin/sh openssl s_client -servername g.api.mega.co.nz -connect g.api.mega.co.nz:443 < /dev/null | sed -n "/-----BEGIN/,/-----END/p" > cert.pem openssl x509 -in cert.pem -pubkey -noout > pubkey.pem openssl asn1parse -noout -inform pem -in pubkey.pem -out pubkey.der echo -n "#define MEGA_NZ_API_PUBKEY_PIN \"sha256//" openssl dgst -sha256 -binary pubkey.der | openssl base64 | tr --delete '\n' echo "\"" rm -f *.pem *.der megatools-1.11.3.20250203/tests/tools/000077500000000000000000000000001474777720000170045ustar00rootroot00000000000000megatools-1.11.3.20250203/tests/tools/.megarc000066400000000000000000000000751474777720000202450ustar00rootroot00000000000000[Login] Username = mt123456@hmamail.com Password = qweqweqwe megatools-1.11.3.20250203/tests/tools/00-test-reg.sh000077500000000000000000000003631474777720000213140ustar00rootroot00000000000000#!/bin/sh source ./config megareg --help # # Use http://www.hidemyass.com/anonymous-email # megareg --register --email mt123456@hmamail.com --name MegaTools --password qweqweqwe # # Continue from there on, as queried on the command line # megatools-1.11.3.20250203/tests/tools/01-test-df.sh000077500000000000000000000001071474777720000211250ustar00rootroot00000000000000#!/bin/sh source ./config megadf --help megadf $OPTS megadf $OPTS -h megatools-1.11.3.20250203/tests/tools/02-test-mkdir.sh000077500000000000000000000004331474777720000216450ustar00rootroot00000000000000#!/bin/sh source ./config megamkdir --help megamkdir $ROPTS /Root/TestDir megamkdir $OPTS /Root/TestDir/SubDir1 megamkdir $OPTS /Root/TestDir/SubDir2 megamkdir $OPTS /Root/TestDir/SubDir3 megamkdir $OPTS /Root/TestDir/SubDir3/SubSubDir megamkdir $OPTS /Contacts/megous@megous.com megatools-1.11.3.20250203/tests/tools/03-test-put.sh000077500000000000000000000005331474777720000213510ustar00rootroot00000000000000#!/bin/sh source ./config megaput --help megarm $ROPTS /Root/TestDir/test.dat /Root/TestDir/test2.dat /Root/TestDir/SubDir1/test2.dat dd if=/dev/urandom of=test.dat bs=267257 count=1 megaput $OPTS --path /Root/TestDir test.dat megaput $OPTS --path /Root/TestDir/test2.dat test.dat megaput $OPTS --path /Root/TestDir/SubDir1/test2.dat test.dat megatools-1.11.3.20250203/tests/tools/04-test-ls.sh000077500000000000000000000002431474777720000211560ustar00rootroot00000000000000#!/bin/sh source ./config megals --help megals $ROPTS megals $OPTS -R --long /Root/TestDir megals $OPTS --long /Root/TestDir megals $OPTS --export /Root/TestDir megatools-1.11.3.20250203/tests/tools/06-test-dl.sh000077500000000000000000000003311474777720000211370ustar00rootroot00000000000000#!/bin/sh source ./config megadl --help LINK=`megals $ROPTS --export /Root/TestDir/test.dat | cut -d ' ' -f 1` rm -f test-dl.dat megadl --path test-dl.dat "$LINK" cmp test.dat test-dl.dat || echo "=== FAILED ===" megatools-1.11.3.20250203/tests/tools/07-test-get.sh000077500000000000000000000002531474777720000213230ustar00rootroot00000000000000#!/bin/sh source ./config megaget --help rm -f test-get.dat megaget $OPTS --path test-get.dat /Root/TestDir/test.dat cmp test.dat test-get.dat || echo "=== FAILED ===" megatools-1.11.3.20250203/tests/tools/08-test-stream.sh000077500000000000000000000005501474777720000220400ustar00rootroot00000000000000#!/bin/sh source ./config LINK=`megals $ROPTS --export /Root/TestDir/test.dat | cut -d ' ' -f 1` rm -f test-stream.dat megaget $OPTS --path - /Root/TestDir/test.dat > test-stream.dat cmp test.dat test-stream.dat || echo "=== FAILED ===" rm -f test-stream.dat megadl --path - "$LINK" > test-stream.dat cmp test.dat test-stream.dat || echo "=== FAILED ===" megatools-1.11.3.20250203/tests/tools/09-test-sync.sh000077500000000000000000000005451474777720000215260ustar00rootroot00000000000000#!/bin/sh source ./config megasync --help # dl rm -rf TestDir megasync $ROPTS --download --remote /Root/TestDir --local TestDir -n megasync $OPTS --download --remote /Root/TestDir --local TestDir # ul megamkdir $OPTS /Root/TestDir/Sub megasync $OPTS --remote /Root/TestDir/Sub --local TestDir -n megasync $OPTS --remote /Root/TestDir/Sub --local TestDir megatools-1.11.3.20250203/tests/tools/10-test-fs-mount.sh000077500000000000000000000000761474777720000223110ustar00rootroot00000000000000#!/bin/sh source ./config mkdir -p Mount megafs $ROPTS Mount megatools-1.11.3.20250203/tests/tools/11-test-fs-umount.sh000077500000000000000000000000531474777720000224720ustar00rootroot00000000000000#!/bin/sh source ./config killall megafs megatools-1.11.3.20250203/tests/tools/12-test-rm.sh000077500000000000000000000002411474777720000211530ustar00rootroot00000000000000#!/bin/sh source ./config megarm --help megals $ROPTS megarm $OPTS /Root/TestDir/SubDir1 megarm $OPTS /Root/TestDir megarm $OPTS /Contacts/megous@megous.com megatools-1.11.3.20250203/tests/tools/config000066400000000000000000000001341474777720000201720ustar00rootroot00000000000000export PATH="../../tools:$PATH" OPTS="--config=.megarc --debug=api" ROPTS="--reload $OPTS" megatools-1.11.3.20250203/tools/000077500000000000000000000000001474777720000156425ustar00rootroot00000000000000megatools-1.11.3.20250203/tools/copy.c000066400000000000000000000202751474777720000167660ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static gchar *opt_remote_path; static gchar *opt_local_path; static gboolean opt_download; static gboolean opt_noprogress; static gboolean opt_dryrun; static gboolean opt_nofollow; static struct mega_session *s; static GOptionEntry entries[] = { { "remote", 'r', 0, G_OPTION_ARG_STRING, &opt_remote_path, "Remote directory", "PATH" }, { "local", 'l', 0, G_OPTION_ARG_STRING, &opt_local_path, "Local directory", "PATH" }, { "download", 'd', 0, G_OPTION_ARG_NONE, &opt_download, "Download files from mega", NULL }, { "no-progress", '\0', 0, G_OPTION_ARG_NONE, &opt_noprogress, "Disable progress bar", NULL }, { "no-follow", '\0', 0, G_OPTION_ARG_NONE, &opt_nofollow, "Don't follow symbolic links", NULL }, { "dryrun", 'n', 0, G_OPTION_ARG_NONE, &opt_dryrun, "Don't perform any actual changes", NULL }, { NULL } }; static gchar *cur_file = NULL; static void status_callback(struct mega_status_data *data, gpointer userdata) { if (!opt_noprogress && data->type == MEGA_STATUS_PROGRESS) tool_show_progress(cur_file, data); } // upload operation static gboolean up_sync_file(GFile *root, GFile *file, const gchar *remote_path) { GError *local_err = NULL; struct mega_node *node = mega_session_stat(s, remote_path); if (node) { g_printerr("ERROR: File already exists at %s\n", remote_path); return FALSE; } g_print("F %s\n", remote_path); if (!opt_dryrun) { g_free(cur_file); cur_file = g_file_get_basename(file); gc_free gchar* local_path = g_file_get_path(file); if (!mega_session_put_compat(s, remote_path, local_path, &local_err)) { if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL); g_printerr("ERROR: Upload failed for %s: %s\n", remote_path, local_err->message); g_clear_error(&local_err); return FALSE; } if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL); } return TRUE; } static gboolean up_sync_dir(GFile *root, GFile *file, const gchar *remote_path) { GError *local_err = NULL; GFileInfo *i; if (root != file) { struct mega_node *node = mega_session_stat(s, remote_path); if (node && node->type == MEGA_NODE_FILE) { g_printerr("ERROR: File already exists at %s\n", remote_path); return FALSE; } if (!node) { g_print("D %s\n", remote_path); if (!opt_dryrun) { if (!mega_session_mkdir(s, remote_path, &local_err)) { g_printerr("ERROR: Can't create remote directory %s: %s\n", remote_path, local_err->message); g_clear_error(&local_err); return FALSE; } } } } // sync children gc_object_unref GFileEnumerator *e = g_file_enumerate_children(file, "standard::*", opt_nofollow ? G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS : G_FILE_QUERY_INFO_NONE, NULL, &local_err); if (!e) { g_printerr("ERROR: Can't read local directory %s: %s\n", g_file_get_relative_path(root, file), local_err->message); g_clear_error(&local_err); return FALSE; } gboolean status = TRUE; while ((i = g_file_enumerator_next_file(e, NULL, NULL))) { const gchar *name = g_file_info_get_name(i); gc_object_unref GFile *child = g_file_get_child(file, name); GFileType type = g_file_query_file_type(child, 0, NULL); gc_free gchar *child_remote_path = g_strconcat(remote_path, "/", name, NULL); if (type == G_FILE_TYPE_DIRECTORY) { if (!up_sync_dir(root, child, child_remote_path)) status = FALSE; } else if (type == G_FILE_TYPE_REGULAR) { if (!up_sync_file(root, child, child_remote_path)) status = FALSE; } else { gc_free gchar* rel_path = g_file_get_relative_path(root, file); g_printerr("WARNING: Skipping special file %s\n", rel_path); } g_object_unref(i); } return status; } // download operation static gboolean dl_sync_file(struct mega_node *node, GFile *file, const gchar *remote_path) { GError *local_err = NULL; gchar *local_path = g_file_get_path(file); if (g_file_query_exists(file, NULL)) { g_printerr("ERROR: File already exists at %s\n", local_path); return FALSE; } g_print("F %s\n", local_path); if (!opt_dryrun) { g_free(cur_file); cur_file = g_file_get_basename(file); if (!mega_session_get_compat(s, g_file_get_path(file), remote_path, &local_err)) { if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL); g_printerr("ERROR: Download failed for %s: %s\n", remote_path, local_err->message); g_clear_error(&local_err); return FALSE; } if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL); } return TRUE; } static gboolean dl_sync_dir(struct mega_node *node, GFile *file, const gchar *remote_path) { GError *local_err = NULL; gc_free gchar *local_path = g_file_get_path(file); if (!g_file_query_exists(file, NULL)) { g_print("D %s\n", local_path); if (!opt_dryrun) { if (!g_file_make_directory(file, NULL, &local_err)) { g_printerr("ERROR: Can't create local directory %s: %s\n", local_path, local_err->message); g_clear_error(&local_err); return FALSE; } } } else { if (g_file_query_file_type(file, 0, NULL) != G_FILE_TYPE_DIRECTORY) { g_printerr("ERROR: Can't create local directory %s: file exists\n", local_path); return FALSE; } } // sync children gboolean status = TRUE; GSList *children = mega_session_get_node_chilren(s, node), *i; for (i = children; i; i = i->next) { struct mega_node *child = i->data; gc_free gchar *child_remote_path = g_strconcat(remote_path, "/", child->name, NULL); gc_object_unref GFile *child_file = g_file_get_child(file, child->name); if (child->type == MEGA_NODE_FILE) { if (!dl_sync_file(child, child_file, child_remote_path)) status = FALSE; } else { if (!dl_sync_dir(child, child_file, child_remote_path)) status = FALSE; } } g_slist_free(children); return status; } // main program static int copy_main(int ac, char *av[]) { gc_object_unref GFile *local_file = NULL; gint status = 0; tool_init(&ac, &av, "- synchronize local and remote mega.nz directories", entries, TOOL_INIT_AUTH | TOOL_INIT_UPLOAD_OPTS | TOOL_INIT_DOWNLOAD_OPTS); if (!opt_local_path || !opt_remote_path) { g_printerr("ERROR: You must specify local and remote paths\n"); goto err; } s = tool_start_session(TOOL_SESSION_OPEN); if (!s) goto err; mega_session_watch_status(s, status_callback, NULL); // check remote dir existence struct mega_node *remote_dir = mega_session_stat(s, opt_remote_path); if (!remote_dir) { g_printerr("ERROR: Remote directory not found %s\n", opt_remote_path); goto err; } else if (!mega_node_is_container(remote_dir)) { g_printerr("ERROR: Remote path must be a folder: %s\n", opt_remote_path); goto err; } // check local dir existence local_file = g_file_new_for_path(opt_local_path); if (opt_download) { if (!dl_sync_dir(remote_dir, local_file, opt_remote_path)) goto err; } else { if (g_file_query_file_type(local_file, 0, NULL) != G_FILE_TYPE_DIRECTORY) { g_printerr("ERROR: Local directory not found %s\n", opt_local_path); goto err; } if (!up_sync_dir(local_file, local_file, opt_remote_path)) status = 1; mega_session_save(s, NULL); } g_free(cur_file); tool_fini(s); return status; err: g_free(cur_file); tool_fini(s); return 1; } const struct shell_tool shell_tool_copy = { .name = "copy", .main = copy_main, .usages = (char*[]){ "[-n] [--no-progress] --local --remote ", "[-n] [--no-progress] --download --local --remote ", NULL }, }; megatools-1.11.3.20250203/tools/df.c000066400000000000000000000067371474777720000164140ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static gboolean opt_human; static gboolean opt_mb; static gboolean opt_gb; static gboolean opt_total; static gboolean opt_free; static gboolean opt_used; static GOptionEntry entries[] = { { "human", 'h', 0, G_OPTION_ARG_NONE, &opt_human, "Use human readable formatting", NULL }, { "mb", '\0', 0, G_OPTION_ARG_NONE, &opt_mb, "Show numbers in MiB", NULL }, { "gb", '\0', 0, G_OPTION_ARG_NONE, &opt_gb, "Show numbers in GiB", NULL }, { "total", '\0', 0, G_OPTION_ARG_NONE, &opt_total, "Show only total available space", NULL }, { "used", '\0', 0, G_OPTION_ARG_NONE, &opt_used, "Show only used space", NULL }, { "free", '\0', 0, G_OPTION_ARG_NONE, &opt_free, "Show only available free space", NULL }, { NULL } }; static gchar *format_size(guint64 size) { if (opt_human) return g_format_size_full(size, G_FORMAT_SIZE_IEC_UNITS); else if (opt_mb) size /= 1024 * 1024; else if (opt_gb) size /= 1024 * 1024 * 1024; return g_strdup_printf("%" G_GUINT64_FORMAT, size); } static int df_main(int ac, char *av[]) { GError *local_err = NULL; struct mega_session *s; tool_init(&ac, &av, "- display mega.nz storage quotas/usage", entries, TOOL_INIT_AUTH); if (opt_total || opt_free || opt_used) { gint opts_used = 0; opts_used += opt_total ? 1 : 0; opts_used += opt_free ? 1 : 0; opts_used += opt_used ? 1 : 0; if (opts_used > 1) { g_printerr("ERROR: Options conflict, you should use either --total, --used, or --free.\n"); return 1; } } if (opt_human || opt_mb || opt_gb) { gint opts_used = 0; opts_used += opt_human ? 1 : 0; opts_used += opt_mb ? 1 : 0; opts_used += opt_gb ? 1 : 0; if (opts_used > 1) { g_printerr("ERROR: Options conflict, you should use either --human, --mb, or --gb.\n"); return 1; } } s = tool_start_session(TOOL_SESSION_OPEN); if (!s) { tool_fini(NULL); return 1; } struct mega_user_quota *q = mega_session_user_quota(s, &local_err); if (!q) { g_printerr("ERROR: Can't determine disk usage: %s\n", local_err->message); g_clear_error(&local_err); goto err; } guint64 free = q->total >= q->used ? q->total - q->used : 0; if (opt_total) g_print("%s\n", format_size(q->total)); else if (opt_used) g_print("%s\n", format_size(q->used)); else if (opt_free) g_print("%s\n", format_size(free)); else { g_print("Total: %s\n", format_size(q->total)); g_print("Used: %s\n", format_size(q->used)); g_print("Free: %s\n", format_size(free)); } g_free(q); tool_fini(s); return 0; err: tool_fini(s); return 1; } struct shell_tool shell_tool_df = { .name = "df", .main = df_main, .usages = (char*[]){ "[--free|--total|--used] [--mb|--gb|-h]", NULL }, }; megatools-1.11.3.20250203/tools/dl.c000066400000000000000000000342111474777720000164060ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" #ifdef G_OS_WIN32 #include #include #endif static gchar *opt_path = "."; static gboolean opt_stream = FALSE; static gboolean opt_noprogress = FALSE; static gboolean opt_print_names = FALSE; static gboolean opt_choose_files = FALSE; static GOptionEntry entries[] = { { "path", '\0', 0, G_OPTION_ARG_FILENAME, &opt_path, "Local directory or file name, to save data to", "PATH" }, { "no-progress", '\0', 0, G_OPTION_ARG_NONE, &opt_noprogress, "Disable progress bar", NULL }, { "print-names", '\0', 0, G_OPTION_ARG_NONE, &opt_print_names, "Print names of downloaded files", NULL }, { "choose-files", '\0', 0, G_OPTION_ARG_NONE, &opt_choose_files, "Choose which files to download when downloading folders (interactive)", NULL }, { NULL } }; static gchar *cur_file; static struct mega_session *s; static void status_callback(struct mega_status_data *data, gpointer userdata) { if (opt_stream && data->type == MEGA_STATUS_DATA) { fwrite(data->data.buf, data->data.size, 1, stdout); fflush(stdout); } if (data->type == MEGA_STATUS_FILEINFO) { g_free(cur_file); cur_file = g_strdup(data->fileinfo.name); } if (!opt_noprogress && data->type == MEGA_STATUS_PROGRESS) tool_show_progress(cur_file, data); } // download operation static gboolean dl_sync_file(struct mega_node *node, GFile *file) { gc_error_free GError *local_err = NULL; gc_object_unref GFile *parent = g_file_get_parent(file); gc_free gchar *local_path = g_file_get_path(file); gc_free gchar *parent_path = g_file_get_path(parent); if (g_file_query_exists(file, NULL)) { g_printerr("ERROR: File already exists at %s\n", local_path); return FALSE; } if (!g_file_query_exists(parent, NULL)) { if (!g_file_make_directory_with_parents(parent, NULL, &local_err)) { g_printerr("ERROR: Can't create local directory %s: %s\n", parent_path, local_err->message); return FALSE; } } else { if (g_file_query_file_type(parent, 0, NULL) != G_FILE_TYPE_DIRECTORY) { g_printerr("ERROR: Can't create local directory %s: a file exists there!\n", parent_path); return FALSE; } } if (!opt_noprogress) g_print("F %s\n", local_path); if (!mega_session_get(s, file, node, &local_err)) { if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL); gc_free gchar* remote_path = mega_node_get_path_dup(node); g_printerr("ERROR: Download failed for %s: %s\n", remote_path, local_err->message); return FALSE; } if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL); if (opt_print_names) g_print("%s\n", local_path); return TRUE; } static gboolean dl_sync_dir(struct mega_node *node, GFile *file) { gc_error_free GError *local_err = NULL; gc_free gchar *local_path = g_file_get_path(file); // sync children GSList *children = mega_session_get_node_chilren(s, node), *i; gboolean status = TRUE; for (i = children; i; i = i->next) { struct mega_node *child = i->data; gc_object_unref GFile *child_file = g_file_get_child(file, child->name); if (child->type == MEGA_NODE_FILE) { if (!dl_sync_file(child, child_file)) status = FALSE; } else { if (!dl_sync_dir(child, child_file)) status = FALSE; } } g_slist_free(children); return status; } static gint* parse_number_list(const gchar* input, gint* count) { *count = 0; gc_regex_unref GRegex* re = g_regex_new("\\s+", 0, 0, NULL); if (re == NULL) return NULL; gchar** tokens = g_regex_split(re, input, 0); guint i = 0, j = 0, n_tokens = g_strv_length(tokens), n_nums = n_tokens; gint* nums = g_new0(gint, n_tokens); *count = 0; while (i < n_tokens) { if (g_regex_match_simple("^\\d{1,7}$", tokens[i], 0, 0)) { nums[*count] = atoi(tokens[i]); *count += 1; } else if (g_regex_match_simple("^\\d{1,7}-\\d{1,7}$", tokens[i], 0, 0)) { gchar** tokens_range = g_regex_split_simple("-", tokens[i], 0, 0); int min = atoi(tokens_range[0]), max = atoi(tokens_range[1]); g_strfreev(tokens_range); if (min > max) { int tmp = max; max = min; min = tmp; } if ((max - min) > 5000) { g_printerr("WARNING: Skipping suspiciously large range '%s'\n", tokens[i]); } else { n_nums += max - min + 1; nums = g_renew(gint, nums, n_nums); for (j = min; j <= max; ++j) { nums[*count] = j; *count += 1; } } } else { g_printerr("WARNING: Skipping non-numeric value '%s'\n", tokens[i]); } i++; } g_strfreev(tokens); return nums; } static GSList *prompt_and_filter_nodes(GSList *nodes) { GSList *chosen_nodes = NULL; gc_free gchar* input = tool_prompt_input(); if (input == NULL) return g_slist_copy(nodes); if (g_regex_match_simple("all", input, G_REGEX_CASELESS, 0)) return g_slist_copy(nodes); gint n_nums; gc_free gint* nums = parse_number_list(input, &n_nums); gint max_index = g_slist_length(nodes); for (int i = 0; i < n_nums; i++) { if (nums[i] <= max_index && nums[i] >= 1) { struct mega_node* node = g_slist_nth_data(nodes, nums[i] - 1); if (!g_slist_find(chosen_nodes, node)) chosen_nodes = g_slist_prepend(chosen_nodes, node); else g_printerr("WARNING: Index %d given multiple times\n", nums[i]); } else g_printerr("WARNING: Index %d out of range\n", nums[i]); } return chosen_nodes; } static gint compare_node(struct mega_node *a, struct mega_node *b) { gchar path1[4096]; gchar path2[4096]; if (mega_node_get_path(a, path1, 4096) && mega_node_get_path(b, path2, 4096)) { if (mega_node_is_container(a)) { int pos = strlen(path1); if(pos < 4095) { path1[pos] = '/'; path1[pos+1] = '\0'; } } if (mega_node_is_container(b)) { int pos = strlen(path2); if(pos < 4095) { path2[pos] = '/'; path2[pos+1] = '\0'; } } return strcmp(path1, path2); } return 0; } static GSList* prune_children(GSList* nodes) { GSList* pruned = NULL, *it, *it2; // not the most optimal, but the working set is small for (it = nodes; it; it = it->next) { struct mega_node *node = it->data; // check if there's ancestor of node in the list for (it2 = nodes; it2; it2 = it2->next) { struct mega_node *node2 = it2->data; if (mega_node_has_ancestor(node, node2)) { gchar path[4096]; if (mega_node_get_path(node, path, sizeof path)) g_printerr("WARNING: skipping already included path %s\n", path); goto prune_node; } } pruned = g_slist_prepend(pruned, node); prune_node:; } g_slist_free(nodes); return g_slist_reverse(pruned); } static GSList* pick_nodes(void) { GSList *nodes = mega_session_ls(s, "/", TRUE), *it, *chosen_nodes; int position = 2; nodes = g_slist_sort(nodes, (GCompareFunc)compare_node); struct mega_node *parent = nodes->data; int indent = 0; g_print("1. %s%s\n", parent->name, mega_node_is_container(parent) ? "/" : ""); for (it = nodes->next; it; it = it->next) { struct mega_node *node = it->data; while(parent != node->parent && indent != 0) { indent--; parent = parent->parent; } g_print("%*s|--" ESC_NORMAL "%d. %s", indent*3, "", position, node->name); if (mega_node_is_container(node)) { g_print("/"); indent++; parent = node; } else { gc_free gchar *size_str = g_format_size_full(node->size, G_FORMAT_SIZE_IEC_UNITS); g_print(" (%s)", size_str); } g_print("\n"); position++; } g_print("Enter numbers of files or folders to download separated by spaces (or type 'all' to download everything, or a range with two numbers separated by '-'):\n> "); chosen_nodes = prompt_and_filter_nodes(nodes); g_slist_free(nodes); // now, user may have chosen both directories and files contained in // them, prune the list chosen_nodes = g_slist_sort(chosen_nodes, (GCompareFunc)compare_node); return prune_children(chosen_nodes); } static gboolean dl_sync_dir_choose(GFile *local_dir) { gc_error_free GError *local_err = NULL; GSList* chosen_nodes = pick_nodes(), *it; gboolean status = TRUE; if (chosen_nodes == NULL) g_printerr("WARNING: Nothing was selected\n"); for (it = chosen_nodes; it; it = it->next) { struct mega_node *node = it->data; gchar remote_path[4096]; if (!mega_node_get_path(node, remote_path, sizeof remote_path)) continue; gc_object_unref GFile *file = g_file_get_child(local_dir, remote_path + 1); if (node->type == MEGA_NODE_FILE) { if (!dl_sync_file(node, file)) status = FALSE; } else { if (!dl_sync_dir(node, file)) status = FALSE; } } g_slist_free(chosen_nodes); return status; } enum { LINK_NONE, LINK_FILE, LINK_FOLDER, }; struct mega_link { int type; gchar *key; gchar *handle; gchar *specific; }; static struct { const char* pattern; int type; GRegex* re; } link_regexes[] = { { "^https?://mega(?:\\.co)?\\.nz/#!([a-z0-9_-]{8})!([a-z0-9_=-]{43}={0,2})$", LINK_FILE }, { "^https?://mega\\.nz/file/([a-z0-9_-]{8})#([a-z0-9_-]{43}={0,2})$", LINK_FILE }, { "^https?://mega(?:\\.co)?\\.nz/#F!([a-z0-9_-]{8})!([a-z0-9_-]{22})(?:[!?]([a-z0-9_-]{8}))?$", LINK_FOLDER }, { "^https?://mega\\.nz/folder/([a-z0-9_-]{8})#([a-z0-9_-]{22})/file/([a-z0-9_-]{8})$", LINK_FOLDER }, { "^https?://mega\\.nz/folder/([a-z0-9_-]{8})#([a-z0-9_-]{22})/folder/([a-z0-9_-]{8})$", LINK_FOLDER }, { "^https?://mega\\.nz/folder/([a-z0-9_-]{8})#([a-z0-9_-]{22})$", LINK_FOLDER }, }; static gboolean parse_link(const char* url, struct mega_link* l) { GMatchInfo *m = NULL; int i; for (i = 0; i < G_N_ELEMENTS(link_regexes); i++) { if (!link_regexes[i].re) { link_regexes[i].re = g_regex_new(link_regexes[i].pattern, G_REGEX_CASELESS, 0, NULL); g_assert(link_regexes[i].re != NULL); } if (g_regex_match(link_regexes[i].re, url, 0, &m)) { l->type = link_regexes[i].type; l->handle = g_match_info_fetch(m, 1); l->key = g_match_info_fetch(m, 2); l->specific = g_match_info_fetch(m, 3); if (l->specific && !l->specific[0]) g_clear_pointer(&l->specific, g_free); g_clear_pointer(&m, g_match_info_unref); return TRUE; } g_clear_pointer(&m, g_match_info_unref); } return FALSE; } static void free_link(struct mega_link* l) { g_free(l->key); g_free(l->handle); g_free(l->specific); memset(l, 0, sizeof(*l)); } static int dl_main(int ac, char *av[]) { gc_error_free GError *local_err = NULL; gint i; int status = 0; tool_init(&ac, &av, "- download exported files from mega.nz", entries, TOOL_INIT_AUTH_OPTIONAL | TOOL_INIT_DOWNLOAD_OPTS); if (!strcmp(opt_path, "-")) { opt_noprogress = opt_stream = TRUE; // see https://github.com/megous/megatools/issues/38 #ifdef G_OS_WIN32 setmode(fileno(stdout), O_BINARY); #endif } if (ac < 2) { g_printerr("ERROR: No links specified for download!\n"); tool_fini(NULL); return 1; } if (opt_stream && ac != 2) { g_printerr("ERROR: Can't stream from multiple files!\n"); tool_fini(NULL); return 1; } // create session s = tool_start_session(TOOL_SESSION_OPEN | TOOL_SESSION_AUTH_ONLY | TOOL_SESSION_AUTH_OPTIONAL); if (!s) { tool_fini(NULL); return 1; } mega_session_watch_status(s, status_callback, NULL); // process links for (i = 1; i < ac; i++) { gc_free gchar *link = g_uri_unescape_string(av[i], NULL); struct mega_link l; if (!parse_link(link, &l)) { g_printerr("WARNING: Skipping invalid Mega download link: %s\n", link); continue; } if (l.type == LINK_FILE) { // perform download if (!mega_session_dl_compat(s, l.handle, l.key, opt_stream ? NULL : opt_path, &local_err)) { if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL "\n"); g_printerr("ERROR: Download failed for '%s': %s\n", link, local_err->message); g_clear_error(&local_err); status = 1; } else { if (!opt_noprogress) { if (tool_is_stdout_tty()) g_print("\r" ESC_CLREOL); g_print("Downloaded %s\n", cur_file); } if (opt_print_names) g_print("%s\n", cur_file); } } else if (l.type == LINK_FOLDER) { if (opt_stream) { g_printerr("ERROR: Can't stream from a directory!\n"); tool_fini(s); return 1; } // perform download if (!mega_session_open_exp_folder(s, l.handle, l.key, l.specific, &local_err)) { g_printerr("ERROR: Can't open folder '%s': %s\n", link, local_err->message); g_clear_error(&local_err); status = 1; } else { mega_session_watch_status(s, status_callback, NULL); GSList *l = mega_session_ls(s, "/", FALSE); if (g_slist_length(l) == 1) { struct mega_node *root_node = l->data; gc_object_unref GFile *local_dir = g_file_new_for_path(opt_path); if (g_file_query_file_type(local_dir, 0, NULL) == G_FILE_TYPE_DIRECTORY) { if (opt_choose_files) { if (!dl_sync_dir_choose(local_dir)) status = 1; } else { if (root_node->type == MEGA_NODE_FILE) { gc_object_unref GFile *local_path = g_file_get_child(local_dir, root_node->name); if (!dl_sync_file(root_node, local_path)) status = 1; } else { if (!dl_sync_dir(root_node, local_dir)) status = 1; } } } else { g_printerr("ERROR: %s must be a directory\n", opt_path); status = 1; } } else { g_printerr("ERROR: EXP folder fs has multiple toplevel nodes? Weird!\n"); status = 1; } g_slist_free(l); } } else { g_printerr("WARNING: Skipping invalid Mega download link type: %s\n", link); } free_link(&l); } tool_fini(s); return status; } const struct shell_tool shell_tool_dl = { .name = "dl", .main = dl_main, .usages = (char*[]){ "[--no-progress] [--path ] ...", "--path - ", NULL }, }; megatools-1.11.3.20250203/tools/export.c000066400000000000000000000046301474777720000173320ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2019 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static GOptionEntry entries[] = { { NULL } }; static int export_main(int ac, char *av[]) { struct mega_session *s; gc_error_free GError *local_err = NULL; GSList* l = NULL, *i; int ret = 1; tool_init(&ac, &av, "- create public link for files at mega.nz", entries, TOOL_INIT_AUTH); if (ac < 2) { g_printerr("ERROR: You must pass at least one remote path\n"); tool_fini(NULL); return 1; } s = tool_start_session(TOOL_SESSION_OPEN); if (!s) { tool_fini(NULL); return 1; } for (gint j = 1; j < ac; j++) { gchar *path = av[j]; struct mega_node *n = mega_session_stat(s, path); if (!n) { g_printerr("ERROR: Remote file not found: %s\n", path); goto out_slist_free; } else if (n->type != MEGA_NODE_FILE) { g_printerr("ERROR: Remote path is not a file: %s\n", path); goto out_slist_free; } l = g_slist_prepend(l, n); } l = g_slist_reverse(l); if (!mega_session_addlinks(s, l, &local_err)) { g_printerr("ERROR: Can't read links info from mega.nz: %s\n", local_err->message); goto out_slist_free; } for (i = l; i; i = i->next) { struct mega_node* n = i->data; if (n->link) { gc_free gchar* link = mega_node_get_link(n, TRUE); g_print("%s\n", link); } else { gc_free gchar *node_path = mega_node_get_path_dup(n); g_printerr("WARNING: Missing link for %s\n", node_path); } } ret = 0; out_slist_free: g_slist_free(l); tool_fini(s); return ret; } const struct shell_tool shell_tool_export = { .name = "export", .main = export_main, .usages = (char*[]){ "...", NULL }, }; megatools-1.11.3.20250203/tools/get.c000066400000000000000000000067071474777720000165770ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static gchar *opt_path = "."; static gboolean opt_stream = FALSE; static gboolean opt_noprogress = FALSE; static GOptionEntry entries[] = { { "path", '\0', 0, G_OPTION_ARG_FILENAME, &opt_path, "Local directory or file name, to save data to", "PATH" }, { "no-progress", '\0', 0, G_OPTION_ARG_NONE, &opt_noprogress, "Disable progress bar", NULL }, { NULL } }; static gchar *cur_file = NULL; static void status_callback(struct mega_status_data *data, gpointer userdata) { if (opt_stream && data->type == MEGA_STATUS_DATA) { fwrite(data->data.buf, data->data.size, 1, stdout); fflush(stdout); } if (data->type == MEGA_STATUS_FILEINFO) { g_free(cur_file); cur_file = g_strdup(data->fileinfo.name); } if (!opt_noprogress && data->type == MEGA_STATUS_PROGRESS) tool_show_progress(cur_file, data); } static int get_main(int ac, char *av[]) { gc_error_free GError *local_err = NULL; struct mega_session *s; gint i, status = 0; tool_init(&ac, &av, "- download individual files from mega.nz", entries, TOOL_INIT_AUTH | TOOL_INIT_DOWNLOAD_OPTS); if (!strcmp(opt_path, "-")) opt_noprogress = opt_stream = TRUE; if (ac < 2) { g_printerr("ERROR: No files specified for download!\n"); tool_fini(NULL); return 1; } if (opt_stream && ac != 2) { g_printerr("ERROR: Can't stream from multiple files!\n"); tool_fini(NULL); return 1; } s = tool_start_session(TOOL_SESSION_OPEN); if (!s) { tool_fini(NULL); return 1; } // download files mega_session_watch_status(s, status_callback, NULL); // stream file if (opt_stream) { struct mega_node *n = mega_session_stat(s, av[1]); if (!n) { g_printerr("ERROR: Remote file not found: %s", av[1]); return FALSE; } if (!mega_session_get(s, NULL, n, &local_err)) { g_printerr("ERROR: Download failed for '%s': %s\n", av[1], local_err->message); g_clear_error(&local_err); status = 1; } tool_fini(s); return status; } for (i = 1; i < ac; i++) { gchar *path = av[i]; // perform download if (!mega_session_get_compat(s, opt_path, path, &local_err)) { if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL "\n"); g_printerr("ERROR: Download failed for '%s': %s\n", path, local_err->message); g_clear_error(&local_err); status = 1; } else { if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL); g_print("Downloaded %s\n", cur_file); } } tool_fini(s); return status; } const struct shell_tool shell_tool_get = { .name = "get", .main = get_main, .usages = (char*[]){ "[--no-progress] [--path ] ...", "--path - ", NULL }, }; megatools-1.11.3.20250203/tools/ls.c000066400000000000000000000114741474777720000164330ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static gboolean opt_names; static gboolean opt_recursive; static gboolean opt_long; static gboolean opt_human; static gboolean opt_export; //static gboolean opt_color; static gboolean opt_header; static gboolean opt_print0; static GOptionEntry entries[] = { { "names", 'n', 0, G_OPTION_ARG_NONE, &opt_names, "List names of files only (will be disabled if you specify multiple paths)", NULL }, { "recursive", 'R', 0, G_OPTION_ARG_NONE, &opt_recursive, "List files in subdirectories", NULL }, { "long", 'l', 0, G_OPTION_ARG_NONE, &opt_long, "Use a long listing format", NULL }, { "header", '\0', 0, G_OPTION_ARG_NONE, &opt_header, "Show columns header in long listing", NULL }, { "human", 'h', 0, G_OPTION_ARG_NONE, &opt_human, "Human readable sizes", NULL }, { "print0", '0', 0, G_OPTION_ARG_NONE, &opt_print0, "Separate file paths with NULs", NULL }, //{ "color", 'c', 0, G_OPTION_ARG_NONE, &opt_color, "Use color highlighting of node types", NULL }, { "export", 'e', 0, G_OPTION_ARG_NONE, &opt_export, "Show mega.nz download links (export)", NULL }, { NULL } }; static gint compare_node(struct mega_node *a, struct mega_node *b) { gchar path1[4096]; gchar path2[4096]; if (mega_node_get_path(a, path1, 4096) && mega_node_get_path(b, path2, 4096)) return strcmp(path1, path2); return 0; } static int ls_main(int ac, char *av[]) { struct mega_session *s; gc_error_free GError *local_err = NULL; GSList *l = NULL, *i; gint j; tool_init(&ac, &av, "- list files stored at mega.nz", entries, TOOL_INIT_AUTH); if (opt_long && opt_print0) { g_printerr("ERROR: You can't combine --print0 and --long\n"); tool_fini(NULL); return 1; } s = tool_start_session(TOOL_SESSION_OPEN); if (!s) { tool_fini(NULL); return 1; } // gather nodes if (ac == 1) { l = mega_session_ls_all(s); opt_names = FALSE; } else { if (ac > 2 || opt_recursive) opt_names = FALSE; for (j = 1; j < ac; j++) { gchar *path = av[j]; struct mega_node *n = mega_session_stat(s, path); if (n && (n->type == MEGA_NODE_FILE || !opt_names)) l = g_slist_append(l, n); l = g_slist_concat(l, mega_session_ls(s, path, opt_recursive)); } } l = g_slist_sort(l, (GCompareFunc)compare_node); // export if requested if (opt_export && !mega_session_addlinks(s, l, &local_err)) { g_printerr("ERROR: Can't read links info from mega.nz: %s\n", local_err->message); g_slist_free(l); tool_fini(s); return 1; } if (l && opt_long && opt_header && !opt_export) { g_print("===================================================================================\n"); g_print("%-11s %-11s %-1s %13s %-19s %s\n", "Handle", "Owner", "T", "Size", "Mod. Date", opt_names ? "Filename" : "Path"); g_print("===================================================================================\n"); } for (i = l; i; i = i->next) { struct mega_node *n = i->data; gc_free gchar *node_path = mega_node_get_path_dup(n); if (opt_export) g_print("%-70s ", n->link ? mega_node_get_link(n, TRUE) : ""); if (opt_long) { GDateTime *dt = g_date_time_new_from_unix_local(n->timestamp); gc_free gchar *time_str = g_date_time_format(dt, "%Y-%m-%d %H:%M:%S"); g_date_time_unref(dt); gc_free gchar *size_str = NULL; if (opt_human) size_str = n->size > 0 ? g_format_size_full(n->size, G_FORMAT_SIZE_IEC_UNITS) : g_strdup("-"); else size_str = n->size > 0 ? g_strdup_printf("%" G_GUINT64_FORMAT, n->size) : g_strdup("-"); g_print("%-11s %-11s %d %13s %19s %s\n", n->handle, n->user_handle ? n->user_handle : "", n->type, size_str, n->timestamp > 0 ? time_str : "", opt_names ? n->name : node_path); } else { g_print("%s", opt_names ? n->name : node_path); if (opt_print0) putchar('\0'); else g_print("\n"); } } g_slist_free(l); tool_fini(s); return 0; } const struct shell_tool shell_tool_ls = { .name = "ls", .main = ls_main, .usages = (char*[]){ "[-e] [-h] [--header] [-l] [-R] [-n] [...]", NULL }, }; megatools-1.11.3.20250203/tools/mkdir.c000066400000000000000000000034271474777720000171220ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static struct mega_session *s; static GOptionEntry entries[] = { { NULL } }; static int mkdir_main(int ac, char *av[]) { gc_error_free GError *local_err = NULL; tool_init(&ac, &av, "- create directories at mega.nz", entries, TOOL_INIT_AUTH); if (ac < 2) { g_printerr("ERROR: No directories specified!\n"); tool_fini(NULL); return 1; } s = tool_start_session(TOOL_SESSION_OPEN); if (!s) { tool_fini(NULL); return 1; } gint i, status = 0; for (i = 1; i < ac; i++) { gchar *path = av[i]; if (!mega_session_mkdir(s, path, &local_err)) { g_printerr("ERROR: Can't create directory %s: %s\n", path, local_err->message); g_clear_error(&local_err); status = 1; } } mega_session_save(s, NULL); tool_fini(s); return status; } const struct shell_tool shell_tool_mkdir = { .name = "mkdir", .main = mkdir_main, .usages = (char*[]){ "...", "/Contacts/", NULL }, }; megatools-1.11.3.20250203/tools/put.c000066400000000000000000000053451474777720000166250ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static gchar *opt_path = "/Root"; static gboolean opt_noprogress = FALSE; static GOptionEntry entries[] = { { "path", '\0', 0, G_OPTION_ARG_STRING, &opt_path, "Remote path to save files to", "PATH" }, { "no-progress", '\0', 0, G_OPTION_ARG_NONE, &opt_noprogress, "Disable progress bar", NULL }, { NULL } }; static gchar *cur_file = NULL; static void status_callback(struct mega_status_data *data, gpointer userdata) { if (!opt_noprogress && data->type == MEGA_STATUS_PROGRESS) tool_show_progress(cur_file, data); } static int put_main(int ac, char *av[]) { gc_error_free GError *local_err = NULL; struct mega_session *s; tool_init(&ac, &av, "- upload files to mega.nz", entries, TOOL_INIT_AUTH | TOOL_INIT_UPLOAD_OPTS); if (ac < 2) { g_printerr("ERROR: No files specified for upload!\n"); tool_fini(NULL); return 1; } s = tool_start_session(TOOL_SESSION_OPEN); if (!s) { tool_fini(NULL); return 1; } mega_session_watch_status(s, status_callback, NULL); gint status = 0; gint i; for (i = 1; i < ac; i++) { gchar *path = av[i]; g_free(cur_file); cur_file = g_path_get_basename(path); // perform download if (!mega_session_put_compat(s, opt_path, path, &local_err)) { if (!opt_noprogress && tool_is_stdout_tty()) g_print("\r" ESC_CLREOL "\n"); if (g_error_matches(local_err, MEGA_ERROR, MEGA_ERROR_EXISTS)) { status = 2; } else { status = 1; } g_printerr("ERROR: Upload failed for '%s': %s\n", path, local_err->message); g_clear_error(&local_err); } else { if (!opt_noprogress) { if (tool_is_stdout_tty()) g_print("\r" ESC_CLREOL); g_print("Uploaded %s\n", cur_file); } } } mega_session_save(s, NULL); tool_fini(s); return status; } const struct shell_tool shell_tool_put = { .name = "put", .main = put_main, .usages = (char*[]){ "[--no-progress] [--path ] ...", NULL }, }; megatools-1.11.3.20250203/tools/reg.c000066400000000000000000000133001474777720000165600ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static gchar *opt_name; static gchar *opt_email; static gchar *opt_password; static gboolean opt_register; static gchar *opt_verify; static gboolean opt_script; static GOptionEntry entries[] = { { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Your real name", "NAME" }, { "email", 'e', 0, G_OPTION_ARG_STRING, &opt_email, "Your email (will be your username)", "EMAIL" }, { "password", 'p', 0, G_OPTION_ARG_STRING, &opt_password, "Your password", "PASSWORD" }, { "register", '\0', 0, G_OPTION_ARG_NONE, &opt_register, "Perform registration", NULL }, { "verify", '\0', 0, G_OPTION_ARG_STRING, &opt_verify, "Finish registration (pass verification link)", "STATE" }, { "scripted", '\0', 0, G_OPTION_ARG_NONE, &opt_script, "Return script friendly output from --register", NULL }, { NULL } }; static gchar *serialize_reg_state(struct mega_reg_state *state) { gc_free gchar *pk = g_base64_encode(state->password_key, 16); gc_free gchar *ch = g_base64_encode(state->challenge, 16); return g_strdup_printf("%s:%s:%s", pk, ch, state->user_handle); } static struct mega_reg_state *unserialize_reg_state(const gchar *str) { gc_match_info_unref GMatchInfo *m = NULL; gc_regex_unref GRegex *r = g_regex_new("^([a-z0-9/+=]{24}):([a-z0-9/+=]{24}):(.*)$", G_REGEX_CASELESS, 0, NULL); gsize len; if (!r) return NULL; if (!g_regex_match(r, str, 0, &m)) return NULL; gc_free gchar *pk = g_match_info_fetch(m, 1); gc_free gchar *ch = g_match_info_fetch(m, 2); gc_free guchar *decoded_ch = NULL; gc_free guchar *decoded_pk = NULL; struct mega_reg_state *state = g_new0(struct mega_reg_state, 1); state->user_handle = g_match_info_fetch(m, 3); decoded_pk = g_base64_decode(pk, &len); if (!decoded_pk) goto err; if (len != 16) goto err; memcpy(state->password_key, decoded_pk, 16); decoded_ch = g_base64_decode(ch, &len); if (!decoded_ch) goto err; if (len != 16) goto err; memcpy(state->challenge, decoded_ch, 16); return state; err: g_free(state->user_handle); g_free(state); return NULL; } static int reg_main(int ac, char *av[]) { gc_error_free GError *local_err = NULL; struct mega_reg_state *state = NULL; gc_free gchar *signup_key = NULL; struct mega_session *s; tool_init(&ac, &av, " - register a new mega.nz account", entries, 0); if (opt_verify && opt_register) { g_printerr("ERROR: You must specify either --register or --verify option\n"); return 1; } if (opt_register) { if (!opt_name) { g_printerr("ERROR: You must specify name for your new mega.nz account\n"); return 1; } if (!opt_email) { g_printerr("ERROR: You must specify email for your new mega.nz account\n"); return 1; } if (!opt_password) { g_printerr("ERROR: You must specify password for your new mega.nz account\n"); return 1; } } else if (opt_verify) { if (ac != 2) { g_printerr("ERROR: You must specify signup key and a link from the verification email\n"); return 1; } gc_regex_unref GRegex *r = g_regex_new( "^(?:https?://mega(?:\\.co)?\\.nz/#confirm)?([a-z0-9_-]{80,512})$", G_REGEX_CASELESS, 0, NULL); gc_match_info_unref GMatchInfo *m = NULL; g_assert(r != NULL); if (!g_regex_match(r, av[1], 0, &m)) { g_printerr("ERROR: Invalid verification link or key: '%s'\n", av[1]); return 1; } signup_key = g_match_info_fetch(m, 1); state = unserialize_reg_state(opt_verify); if (!state) { g_printerr( "ERROR: Failed to decode registration state parameter, make sure you copied it correctly\n"); return 1; } } else { g_printerr("ERROR: You must specify either --register or --verify option\n"); return 1; } s = tool_start_session(0); if (!s) { tool_fini(NULL); return 1; } if (opt_register) { if (!mega_session_register(s, opt_email, opt_password, opt_name, &state, &local_err)) { g_printerr("ERROR: Registration failed: %s\n", local_err ? local_err->message : "Unknown error"); goto err; } gc_free gchar *serialized_state = serialize_reg_state(state); if (opt_script) g_print("megatools reg --verify %s @LINK@\n", serialized_state); else g_print("Registration email was sent to %s. To complete registration, you must run:\n\n" " megatools reg --verify %s @LINK@\n\n" "(Where @LINK@ is registration link from the 'MEGA Signup' email)\n", opt_email, serialized_state); } if (opt_verify) { if (!mega_session_register_verify(s, state, signup_key, &local_err)) { g_printerr("ERROR: Verification failed: %s\n", local_err ? local_err->message : "Unknown error"); goto err; } if (!opt_script) g_print("Account registered successfully!\n"); } tool_fini(s); return 0; err: tool_fini(s); return 1; } const struct shell_tool shell_tool_reg = { .name = "reg", .main = reg_main, .usages = (char*[]){ "[--scripted] --register --email --name --password ", "[--scripted] --verify ", NULL }, }; megatools-1.11.3.20250203/tools/rm.c000066400000000000000000000034001474777720000164210ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static GOptionEntry entries[] = { { NULL } }; static int rm_main(int ac, char *av[]) { gc_error_free GError *local_err = NULL; static struct mega_session *s; tool_init(&ac, &av, "- remove files from mega.nz", entries, TOOL_INIT_AUTH); if (ac < 2) { g_printerr("ERROR: No files specified for removal!\n"); tool_fini(NULL); return 1; } s = tool_start_session(TOOL_SESSION_OPEN); if (!s) { tool_fini(NULL); return 1; } gint i, status = 0; for (i = 1; i < ac; i++) { gchar *path = av[i]; if (!mega_session_rm(s, path, &local_err)) { g_printerr("ERROR: Can't remove %s: %s\n", path, local_err->message); g_clear_error(&local_err); status = 1; } } mega_session_save(s, NULL); tool_fini(s); return status; } const struct shell_tool shell_tool_rm = { .name = "rm", .main = rm_main, .usages = (char*[]){ "...", "/Contacts/", NULL }, }; megatools-1.11.3.20250203/tools/shell.c000066400000000000000000000073101474777720000171160ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include "shell.h" #include "config.h" #include "lib/alloc.h" #ifdef G_OS_WIN32 #include #include #include #endif #include extern struct shell_tool shell_tool_df; extern struct shell_tool shell_tool_dl; extern struct shell_tool shell_tool_get; extern struct shell_tool shell_tool_ls; extern struct shell_tool shell_tool_mkdir; extern struct shell_tool shell_tool_put; extern struct shell_tool shell_tool_reg; extern struct shell_tool shell_tool_rm; extern struct shell_tool shell_tool_copy; extern struct shell_tool shell_tool_test; extern struct shell_tool shell_tool_export; static struct shell_tool* tools[] = { &shell_tool_dl, &shell_tool_df, &shell_tool_ls, &shell_tool_test, &shell_tool_export, &shell_tool_get, &shell_tool_put, &shell_tool_copy, &shell_tool_mkdir, &shell_tool_rm, &shell_tool_reg, }; #ifdef G_OS_WIN32 static unsigned int initial_cp; static void print_no_convert(const gchar *buf) { fputs(buf, stdout); fflush(stdout); } static void printerr_no_convert(const gchar *buf) { fputs(buf, stderr); fflush(stderr); } static void restore_console(void) { SetConsoleOutputCP(initial_cp); } #endif int main(int ac, char *av[]) { #ifdef G_OS_WIN32 setlocale(LC_ALL, "C"); av = g_win32_get_command_line(); ac = g_strv_length(av); g_set_print_handler(print_no_convert); g_set_printerr_handler(printerr_no_convert); initial_cp = GetConsoleOutputCP(); SetConsoleOutputCP(CP_UTF8); atexit(restore_console); #else setlocale(LC_ALL, ""); av = g_strdupv(av); #endif gc_free gchar* cmd_basename = g_path_get_basename(av[0]); gchar* cmd_name = NULL; if (g_str_has_suffix(cmd_basename, ".exe")) cmd_basename[strlen(cmd_basename) - 4] = '\0'; if (g_str_has_prefix(cmd_basename, "mega")) cmd_name = cmd_basename + 4; if (cmd_name) { // try to run a specifc if we're run via mega[.exe] for (int i = 0; i < G_N_ELEMENTS(tools); i++) { if (!strcmp(cmd_name, tools[i]->name)) return tools[i]->main(ac, av); } } if (ac > 1) { // otherwise try to find a command name based on the first argument for (int i = 0; i < G_N_ELEMENTS(tools); i++) { if (!strcmp(av[1], tools[i]->name)) { av[1] = g_strdup_printf("megatools %s", av[1]); return tools[i]->main(ac - 1, av + 1); } } } // show usage if we failed to run any specific command g_print("Usage:\n"); for (int i = 0; i < G_N_ELEMENTS(tools); i++) if (tools[i]->usages) for (int j = 0; tools[i]->usages[j]; j++) g_print(" megatools %s %s\n", tools[i]->name, tools[i]->usages[j]); g_print("\n"); g_print("Run: megatools --help for detailed options for each command.\n"); g_print("\n"); g_print("megatools " VERSION " - command line tools for Mega.nz\n"); g_print("Written by Ondrej Jirman , 2013-2025\n"); g_print("Go to https://xff.cz/megatools/ for more information\n"); return 1; } megatools-1.11.3.20250203/tools/shell.h000066400000000000000000000016641474777720000171310ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2013 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include struct shell_tool { gchar* name; int (*main)(int ac, char* av[]); gchar** usages; }; megatools-1.11.3.20250203/tools/test.c000066400000000000000000000042501474777720000167660ustar00rootroot00000000000000/* * megatools - Mega.nz client library and tools * Copyright (C) 2019 Ondřej Jirman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "tools.h" #include "shell.h" static gboolean opt_is_file; static gboolean opt_is_folder; static GOptionEntry entries[] = { { "file", 'f', 0, G_OPTION_ARG_NONE, &opt_is_file, "Test for files only", NULL }, { "folder", 'd', 0, G_OPTION_ARG_NONE, &opt_is_folder, "Test for folders only", NULL }, { NULL } }; static int test_main(int ac, char *av[]) { struct mega_session *s; gc_error_free GError *local_err = NULL; int ret = 0; tool_init(&ac, &av, "- test for existence of files stored at mega.nz", entries, TOOL_INIT_AUTH); if (opt_is_file && opt_is_folder) { g_printerr("ERROR: You can't combine -f and -d\n"); tool_fini(NULL); return 3; } if (ac < 2) { g_printerr("ERROR: You must pass at least one remote path\n"); tool_fini(NULL); return 3; } s = tool_start_session(TOOL_SESSION_OPEN); if (!s) { tool_fini(NULL); return 3; } for (gint j = 1; j < ac; j++) { gchar *path = av[j]; struct mega_node *n = mega_session_stat(s, path); if (!n) { ret = 1; } else if (n->type != MEGA_NODE_FILE && opt_is_file) { if (ret != 1) ret = 2; } else if (n->type != MEGA_NODE_FOLDER && opt_is_folder) { if (ret != 1) ret = 2; } } tool_fini(s); return ret; } const struct shell_tool shell_tool_test = { .name = "test", .main = test_main, .usages = (char*[]){ "[-f|-d] ...", NULL }, };