diff options
Diffstat (limited to 'src/mmsrip')
-rw-r--r-- | src/mmsrip/AUTHORS | 27 | ||||
-rw-r--r-- | src/mmsrip/COPYING | 340 | ||||
-rw-r--r-- | src/mmsrip/ChangeLog | 156 | ||||
-rw-r--r-- | src/mmsrip/NEWS | 68 | ||||
-rw-r--r-- | src/mmsrip/README | 37 | ||||
-rw-r--r-- | src/mmsrip/common.h | 50 | ||||
-rw-r--r-- | src/mmsrip/error.c | 117 | ||||
-rw-r--r-- | src/mmsrip/error.h | 40 | ||||
-rw-r--r-- | src/mmsrip/main.c | 753 | ||||
-rw-r--r-- | src/mmsrip/mms.c | 1239 | ||||
-rw-r--r-- | src/mmsrip/mms.h | 129 |
11 files changed, 2956 insertions, 0 deletions
diff --git a/src/mmsrip/AUTHORS b/src/mmsrip/AUTHORS new file mode 100644 index 0000000..530a73f --- /dev/null +++ b/src/mmsrip/AUTHORS @@ -0,0 +1,27 @@ +Nicolas BENOIT (main developper) +nbenoit@tuxfamily.org +http://nbenoit.tuxfamily.org + +Major MMS (author of mmsclient) +http://www.geocities.com/majormms + +SDP Multimedia (author of the only doc about MMS) +http://get.to/sdp + +Aurelien REQUIEM (author of mmsrip's ebuild script) +aurelien@menfin.net + +Luis COSTA (initial idea and patch for the -d/--delay option) +izhirahider@gmail.com + +Jeff FULMER (Solaris port) +jeff@joedog.org + +Federico SIMONCELLI (RPMs for Fedora Core 4) +federico.simoncelli@gmail.com + +Kyuzz (Fix for Cygwin compilation) +kyuzz.org + +Xavier ROCHE (Initial port to Win32) +roche@httrack.com diff --git a/src/mmsrip/COPYING b/src/mmsrip/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/src/mmsrip/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. + + <signature of Ty Coon>, 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 Library General +Public License instead of this License. diff --git a/src/mmsrip/ChangeLog b/src/mmsrip/ChangeLog new file mode 100644 index 0000000..da17d27 --- /dev/null +++ b/src/mmsrip/ChangeLog @@ -0,0 +1,156 @@ +2006-01-24 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * README, NEWS: getting ready for 0.7.0 release. + * gentoo/mmsrip-0.7.0.ebuild: ebuild script for 0.7.0 release. + * spec/mmsrip.spec: updated for 0.7.0 release. + + +2006-01-23 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/mms.h, src/mms.c, src/main.c, doc/mmsrip.1: renamed compatibility mode to trick enabled mode (-c/--compat switches are now -k/--trick). + * src/mms.c, src/main.c: fixed parsing of urls that look like 'stream.asf?digest=7Q2bjXo&provider=lala'. + * src/mms.c, src/mms.h, src/main.c, doc/mmsrip.1, configure.ac: replaced configure's --enable-debug switch by a -gFILE/--debug=FILE runtime switch. + * doc/mmsrip.1: improved presentation. + + +2006-01-22 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/mms.c: fixed presentation in debug output. + * AUTHORS: updated. + * src/mms.c: improved debug ouput for header interpretation. + * src/mms.c: fixed expected file size. + * src/mms.c: added a few calls to mms_get_32() for code lisibility. + * src/mms.c: added mms_get_64() function (improves lisibility in mms_interp_header()). + * src/mms.c: improved ASF header interpretation. + * src/mms.h, src/mms.c, src/main.c, doc/mmsrip.1: added compatibility mode and -c/--compat switches. + * src/main.c: minor presentation improvements. + * src/mms.c: added an entry for mmsh:// protocol though we don't support it. + + +2006-01-21 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/mms.h, src/mms.c, src/error.c, src/error.h, src/main.c, configure.ac: fixed compilation on Win32 (thanks to Xavier ROCHE for the initial port). + * AUTHORS: updated. + * src/mms.c: fixed URL parsing (mmst:// is now ok). + * src/mms.c, doc/mmsrip.1: the user should read the manpage when the server sends a no auth error. + * configure.ac: added a switch to enable debug output to stdout. + + +2006-01-20 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/mms.h, src/main.c, configure.ac: fixed compilation on Solaris (from Jeff FULMER's Solaris port). + * AUTHORS: updated. + * gentoo/mmsrip-0.6.6.ebuild: ebuild script for 0.6.6 release. + * spec/mmsrip.spec: spec file for RPM building (thanks to Federico SIMONCELLI). + * src/mms.c, configure.ac: added the display of the ripping speed. + + +2006-01-17 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/mms.h: fixed compilation on Cygwin (greetings to Kyuzz for bug reporting). + + +2006-01-06 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * configure.ac, src/common.h: 0.6.5 release. + + +2006-01-06 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/mms.c: fixed a call to error(). + * src/mms.h: reordered some error codes. + * src/main.c: improved the error code returned by the program. + * doc/mmsrip.1: added some documentation about the program's returned value. + * gentoo/mmsrip-0.6.5.ebuild: ebuild script for 0.6.5 release. + * README, NEWS: getting ready for 0.6.5 release. + + +2005-11-23 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/mms.h, src/mms.c, src/main.c: replaced a few values with #defines. + * src/mms.c: added the support for no auth errors (this happens sometimes, for example on canalplus.fr). + + +2005-07-09 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * configure.ac, src/common.h: 0.6.4 release. + + +2005-07-09 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * doc/mmsrip.1: added man page. + * doc/Makefile.am, Makefile.am, configure.ac: added the man page to the package. + * src/main.c: fixed a compilation warning about variable `end` initialization. + * src/mms.c: fixed a potential security issue in the handling of files containing more than 20 streams (patch ported from MPlayer). + * src/mms.c: fixed a bug in the media stream MBR selection that prevented ASF files from being ripped properly (bug reported by Jozef RIHA). + * gentoo/mmsrip-0.6.4.ebuild: ebuild script for 0.6.4 release. + * README, NEWS: getting ready for 0.6.4 release. + + +2005-06-05 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * configure.ac, src/common.h: 0.6.2 release. + + +2005-06-05 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * Makefile.am: ebuild script moved in a dedicated gentoo directory. + * gentoo/mmsrip-0.6.2.ebuild: ebuild script for 0.6.2 release. + * src/main.c: added '-d' switch which makes mmsrip exit after the specified delay (idea and patch by Luis COSTA). + * src/main.c: fixed bug that made mmsrip attempt to use invalid URLs. + * AUTHORS: updated. + * src/main.c: added support for long options. + * README, NEWS: getting ready for 0.6.2 release. + + +2005-05-29 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * configure.ac: 0.6.0 release. + + +2005-05-29 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/main.c: added '-q' switch which makes mmsrip quiet. + * src/mms.c, src/mms.h: added support for quiet mode. + * src/main.c: added '-t' switch which makes mmsrip check stream availability only. + * src/main.c: added '-o' switch which makes mmsrip output stream to specified file. + * src/main.c: fixed a bug in args handling. + * README, NEWS, src/common.h: getting ready for 0.6.0 release. + + +2005-05-28 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * mmsrip-0.6.0.ebuild: added Aurelien REQUIEM's ebuild. + * README: updated. + * AUTHORS: updated. + * Makefile.am: added ebuild script to EXTRA_DIST. + * configure.ac: added CVS infos. + + +2005-02-21 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * configure.ac, src/common.h: 0.4.2 release. + + +2005-02-21 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/mms.c: Removed bad EOF handling in mms_recv_packet(). + * README, NEWS: Getting ready for 0.4.2. + + +2005-02-20 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * configure.ac, src/common.h: 0.4.1 release. + + +2005-02-20 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * src/main.c: Fixed bug with multiple streams (mmsrip used to download the same stream every time). + * configure.ac: Removed unnecessary checks. + * src/mms.c: Removed strndup() because a lot of people don't have it. + * README, NEWS: Getting ready for 0.4.1. + + +2005-02-20 Nicolas BENOIT <nbenoit@tuxfamily.org> + + * configure.ac, src/common.h: 0.4.0 release. diff --git a/src/mmsrip/NEWS b/src/mmsrip/NEWS new file mode 100644 index 0000000..43b54aa --- /dev/null +++ b/src/mmsrip/NEWS @@ -0,0 +1,68 @@ + MMS Ripper v0.7.0: +==================== + + - Added '-k' and '--trick' switches that activate a trick for some recalcitrant servers. + - Added the display of the ripping speed. + - Added '-gFILE' and '--debug=FILE' switches which activate debug output to specified file. + - Fixed parsing of urls that include options after file name (stream.asf?dig=7QXo&pr=lala). + - Fixed protocol checking in URL parsing. + - Fixed expected file size calculation. + - Fixed compilation on Solaris, Cygwin and Win32 (check AUTHORS file for greetings). + - Improved debug output. + - Improved ASF header interpretation. + - Improved presentation in manpage. + - Minor code cleanups. + - A spec file is now distributed. + + + MMS Ripper v0.6.5: +==================== + + - Added the handling of the no auth error during streaming. + (this should fix mmsrip behavior on some servers such as canalplus.fr) + - Improved the diagnostic value returned by mmsrip in case of error (please read the manpage). + + + MMS Ripper v0.6.4: +==================== + + - Added a man page. + - Fixed a compilation warning. + - Fixed a potential security issue. + - Fixed the ripping of some ASF files (bug reported by Jozef RIHA). + + + MMS Ripper v0.6.2: +==================== + + - Added '-d' switch which makes mmsrip exit after DELAY seconds. + - Added support for long options. + - Fixed bug that made mmsrip attempt to use invalid URLs. + + + MMS Ripper v0.6.0: +==================== + + - Added '-q' switch which enables quiet mode. + - Added '-t' switch which makes mmsrip check stream availability only. + - Added '-o' switch so the user can choose the output file for every stream. + - Fixed a bug in arguments handling. + + + MMS Ripper v0.4.2: +==================== + + - Fixed a serious bug in packet reception (mmsrip went crazy). + + + MMS Ripper v0.4.1: +==================== + + - Fixed a bug that made mmsrip download the same stream every time. + - Compilation fixes. + + + MMS Ripper v0.4.0: +==================== + + - First Release of mmsrip. diff --git a/src/mmsrip/README b/src/mmsrip/README new file mode 100644 index 0000000..bf7f7d1 --- /dev/null +++ b/src/mmsrip/README @@ -0,0 +1,37 @@ + MMS Ripper release 0.7.0 + +These are the release notes for mmsrip version 0.7.0 +Read them carefully, as they tell you what this is all about. + + +WHAT IS MMSRIP ? + + MMSRIP allows you to save on your hard-disk the content being streamed + by an MMS server. This program has been written for personnal use, so + don't blame me if you think I am stupid doing such tool for the others. + + It should run on every POSIX compliant Operating System, but I can't + give you any complete list. + + +HOW TO INSTALL ? + + Read the file INSTALL in order to get the answer of this question... ;) + If you use a gentoo based distribution, enjoy the ebuild script contributed by Aurelien REQUIEM. + If you use a RPM based distribution, you may use the spec file provided by Frederico SIMONCELLI. + + +HOW TO RUN ? + + Once you have compiled & installed, you should be able to run the program. + + +IMPORTANT NOTE + + All the credits go to SDP Multimedia and Major MMS. + + +-- + +$RCSfile: README,v $ +$Date: 2006/01/24 18:13:07 $ - $Revision: 1.13 $ diff --git a/src/mmsrip/common.h b/src/mmsrip/common.h new file mode 100644 index 0000000..47f5567 --- /dev/null +++ b/src/mmsrip/common.h @@ -0,0 +1,50 @@ +/* + * $RCSfile: common.h,v $ + * $Date: 2006/01/17 19:59:27 $ - $Revision: 1.11 $ + * + * This file is distributed as a part of MMSRIP ( MMS Ripper ). + * Copyright (c) 2005-2006 Nicolas BENOIT + * + * This program is free software; you can 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#else +#define PROGRAM_SHORT_NAME "mmsrip" +#define PROGRAM_FULL_NAME "MMS Ripper" +#define PROGRAM_VERSION "0.7.0-rc1" +#endif + +#define PROGRAM_AUTHORS "Nicolas BENOIT <nbenoit@tuxfamily.org>" +#define PROGRAM_HELPERS "SDP Multimedia and Major MMS" + + +/* + * quick struct in order to make list of streams + */ +typedef struct +{ + void *next; + char *stream; + char *output; +} STREAM_LIST; + +#endif diff --git a/src/mmsrip/error.c b/src/mmsrip/error.c new file mode 100644 index 0000000..9d2f4c9 --- /dev/null +++ b/src/mmsrip/error.c @@ -0,0 +1,117 @@ +/* + * $RCSfile: error.c,v $ + * $Date: 2006/01/21 20:09:57 $ - $Revision: 1.7 $ + * + * This file is distributed as a part of MMSRIP ( MMS Ripper ). + * Copyright (c) 2005-2006 Nicolas BENOIT + * + * This program is free software; you can 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "common.h" +#include "error.h" + + +/* + * Prints A Warning + */ +#ifdef HAVE_VSNPRINTF +void +warning ( const char *prod, + const char *format, + ... ) +{ + char *message; + va_list ap; + + if ( format == NULL ) + return; + + message = ( char * ) malloc ( ERROR_MSG_LEN ); + va_start ( ap, format ); + vsnprintf ( message, ERROR_MSG_LEN, format, ap ); + va_end ( ap ); + + if ( prod != NULL ) + fprintf ( stderr, "warning in %s(): %s.\n", prod, message ); + else + fprintf ( stderr, "warning: %s.\n", message ); + + free ( message ); +} +#else +void +warning ( const char *prod, + const char *message ) +{ + if ( message == NULL ) + return; + + if ( prod != NULL ) + fprintf ( stderr, "warning in %s(): %s.\n", prod, message ); + else + fprintf ( stderr, "warning: %s.\n", message ); +} +#endif + + +/* + * Prints An Error Message + */ +#ifdef HAVE_VSNPRINTF +void +error ( const char *prod, + const char *format, + ... ) +{ + char *message; + va_list ap; + + if ( format == NULL ) + return; + + message = ( char * ) malloc ( ERROR_MSG_LEN ); + va_start ( ap, format ); + vsnprintf ( message, ERROR_MSG_LEN, format, ap ); + va_end ( ap ); + + if ( prod != NULL ) + fprintf ( stderr, "error in %s(): %s.\n", prod, message ); + else + fprintf ( stderr, "error: %s.\n", message ); + + free ( message ); +} +#else +void +error ( const char *prod, + const char *message ) +{ + if ( message == NULL ) + return; + + if ( prod != NULL ) + fprintf ( stderr, "error in %s(): %s.\n", prod, message ); + else + fprintf ( stderr, "error: %s.\n", message ); +} +#endif diff --git a/src/mmsrip/error.h b/src/mmsrip/error.h new file mode 100644 index 0000000..d0e379c --- /dev/null +++ b/src/mmsrip/error.h @@ -0,0 +1,40 @@ +/* + * $RCSfile: error.h,v $ + * $Date: 2006/01/21 20:09:57 $ - $Revision: 1.4 $ + * + * This file is distributed as a part of MMSRIP ( MMS Ripper ). + * Copyright (c) 2005-2006 Nicolas BENOIT + * + * This program is free software; you can 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#ifndef __ERROR_H__ +#define __ERROR_H__ + +#ifdef HAVE_VSNPRINTF +#include <stdarg.h> + +#define ERROR_MSG_LEN 128 + +void warning ( const char *, const char *, ... ); +void error ( const char *, const char *, ... ); +#else +void warning ( const char *, const char * ); +void error ( const char *, const char * ); +#endif + +#endif diff --git a/src/mmsrip/main.c b/src/mmsrip/main.c new file mode 100644 index 0000000..a575b4a --- /dev/null +++ b/src/mmsrip/main.c @@ -0,0 +1,753 @@ +/* + * $RCSfile: main.c,v $ + * $Date: 2006/01/23 20:30:42 $ - $Revision: 1.32 $ + * + * This file is distributed as a part of MMSRIP ( MMS Ripper ). + * Copyright (c) 2005-2006 Nicolas BENOIT + * + * This program is free software; you can 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <time.h> +#include <sys/time.h> +#include "common.h" +#include "mms.h" +#include "error.h" + +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif + +/* + * options + */ +#if defined(SOLARIS) || defined(sun) +static char * options = "ahvtqko:d:g:"; +#else +static char * options = "-ahvtqko:d:g:"; +#endif + +#ifdef HAVE_GETOPT_LONG +static struct option long_options[] = { + {"about", 0, NULL, 'a'}, + {"version", 0, NULL, 'v'}, + {"help", 0, NULL, 'h'}, + {"test", 0, NULL, 't'}, + {"quiet", 0, NULL, 'q'}, + {"trick", 0, NULL, 'k'}, + {"output", 1, NULL, 'o'}, + {"delay", 1, NULL, 'd'}, + {"debug", 1, NULL, 'g'}, + {NULL, 0, NULL, 0 } +}; +#endif + +/* + * usage + */ +void +usage ( void ) +{ + fprintf ( stderr, "%s (%s) version %s\n\n", PROGRAM_SHORT_NAME, PROGRAM_FULL_NAME, PROGRAM_VERSION ); + fprintf ( stderr, "usage: %s <[-oFILE] stream url> ...\n\n", PROGRAM_SHORT_NAME ); + +#ifdef HAVE_GETOPT_LONG + fprintf ( stderr, "General Options:\n\t" \ + "-a, --about\t\tshow information about %s\n\t" \ + "-h, --help\t\tshow this help\n\t" \ + "-v, --version\t\tshow version number\n\n" \ + "Program Behaviour:\n\t" \ + "-oFILE, --output=FILE\toutput to specified file (can be repeated)\n\t" \ + "-gFILE, --debug=FILE\toutput debug info to specified file\n\t" \ + "-q, --quiet\t\tquiet mode (can be repeated)\n\t" \ + "-dDELAY, --delay=DELAY\tsave the stream during DELAY seconds and exit\n\t" \ + "-k, --trick\t\tattempt to trick recalcitrant servers\n\t" \ + "-t, --test\t\ttest mode (check stream availability)\n\n", PROGRAM_SHORT_NAME ); +#else + fprintf ( stderr, "General Options:\n\t" \ + "-a\tshow information about %s\n\t" \ + "-h\tshow this help\n\t" \ + "-v\tshow version number\n\n" \ + "Program Behaviour:\n\t" \ + "-oFILE\toutput to specified file (can be repeated)\n\t" \ + "-gFILE\toutput debug info to specified file\n\t" \ + "-q\tquiet mode (can be repeated)\n\t" \ + "-dDELAY\tsave the stream during DELAY seconds and exit\n\t" \ + "-k\tattempt to trick recalcitrant servers\n\t" \ + "-t\ttest mode (check stream availability)\n\n", PROGRAM_SHORT_NAME ); +#endif + + return; +} + + +/* + * main + */ +int +main ( int argc, + char *argv[] ) +{ + MMS *mms; + FILE *f; + FILE *stddebug = NULL; + char *output_filename; + STREAM_LIST *stream_list; + STREAM_LIST *block; + char c; + int ret = MMS_RET_SUCCESS; + int quiet = 0; + int trick = MMS_TRICK_DISABLED; + int retry = 0; + int test = 0; + int delay = 0; + time_t end = 0; + float speed = 0.0f; + struct timeval speed_last_update; + struct timeval now; + float elapsed_time; + ssize_t len_written; + uint64_t total_len_written = 0; + uint64_t old_total_len_written = 0; + + if ( ( stream_list = (STREAM_LIST *) malloc(sizeof(STREAM_LIST)) ) == NULL ) + { + error ( "main", "early initialization failed" ); + return 1; + } + + stream_list->next = NULL; + stream_list->stream = NULL; + stream_list->output = NULL; + + block = stream_list; + +#ifdef HAVE_GETOPT_LONG + while ( ( c = getopt_long(argc, argv, options, long_options, NULL) ) != -1 ) +#elif defined(SOLARIS) || defined(sun) + /* Implementation of getopt in Solaris is a bit strange, it returns -1 even if there are still args to parse... */ + while ( optind < argc ) +#else + while ( ( c = getopt(argc, argv, options) ) != -1 ) +#endif + { +#if defined(SOLARIS) || defined(sun) + c = getopt ( argc, argv, options ); +#endif + switch (c) + { + case 'h': + { + fprintf ( stderr, "\n" ); + usage(); + return 0; + } + + case 'v': + { + fprintf ( stderr, "%s version %s\n", PROGRAM_SHORT_NAME, PROGRAM_VERSION); + return 0; + } + + case 'a': + { + fprintf ( stderr, "\n" ); + fprintf ( stderr, "%s (%s) version %s\n\n", PROGRAM_SHORT_NAME, PROGRAM_FULL_NAME, PROGRAM_VERSION); + fprintf ( stderr, "Written by %s.\n", PROGRAM_AUTHORS ); + fprintf ( stderr, "With a lot of help from %s.\n\n", PROGRAM_HELPERS ); + fprintf ( stderr, "This program is free software; you can redistribute it and/or\nmodify it under the terms " \ + "of the GNU General Public License\nas published by the Free Software Foundation; either version 2" \ + ",\nor (at your option) any later version.\n\n" ); + return 0; + } + + case 't': + { + test = 1; + break; + } + + case 'q': + { + quiet += 1; + break; + } + + case 'k': + { + trick = MMS_TRICK_ENABLED; + break; + } + + case 'o': + { + if ( optarg != NULL ) + { + if ( block->stream != NULL ) + { + if ( ( block->next = malloc ( sizeof(STREAM_LIST) ) ) == NULL ) + { + error ( "main", "early initialization failed" ); + ret = MMS_RET_ERROR; + goto clean; + } + + block = block->next; + block->next = NULL; + block->stream = NULL; + } + + if ( block->output != NULL ) + free ( block->output ); + + block->output = (char *) strdup ( optarg ); + } + + break; + } + + case 'g': + { + if ( optarg != NULL ) + { + if ( stddebug != NULL ) + fclose ( stddebug ); + + if ( ( stddebug = fopen ( optarg, "w" ) ) == NULL ) + { + if ( quiet < 2 ) +#ifdef HAVE_VSNPRINTF + warning ( NULL, "unable to write debug info in \'%s\'", optarg ); +#else + warning ( NULL, "unable to write debug info in specified file" ); +#endif + } + else + { + fprintf ( stddebug, "%s (%s) version %s\n\n", PROGRAM_SHORT_NAME, PROGRAM_FULL_NAME, PROGRAM_VERSION); + fprintf ( stddebug, "--> debug log begins now\n" ); + } + } + + break; + } + + case 'd': + { + if ( optarg != NULL ) + { + delay = atoi( optarg ); + + if ( delay < 0 ) + delay = 0; + } + + break; + } + + +#if defined(SOLARIS) || defined(sun) + case -1: + { + if ( optind >= argc ) + break; +#else + case 1: + { + if ( optarg != NULL ) + { +#endif + if ( block->stream != NULL ) + { + if ( ( block->next = malloc ( sizeof(STREAM_LIST) ) ) == NULL ) + { + error ( "main", "early initialization failed" ); + ret = MMS_RET_ERROR; + goto clean; + } + + block = block->next; + block->next = NULL; + block->output = NULL; + } +#if defined(SOLARIS) || defined(sun) + /* optind is not incremented when meeting something else than an option, so we do that... */ + block->stream = (char *) strdup ( argv[optind++] ); +#else + block->stream = (char *) strdup ( optarg ); + } +#endif + break; + } + } + } + + if ( stream_list->stream == NULL ) + { + usage ( ); + ret = MMS_RET_ERROR; + goto clean; + } + + if ( !quiet ) + { + fprintf ( stderr, "\n" ); + fprintf ( stderr, "%s (%s) version %s\n\n", PROGRAM_SHORT_NAME, PROGRAM_FULL_NAME, PROGRAM_VERSION ); + } + + for ( block=stream_list; block!=NULL; block=(STREAM_LIST*)block->next ) + { + if ( block->stream == NULL ) + { + if ( quiet < 2 ) + { + if ( block->output == NULL ) +#ifdef HAVE_VSNPRINTF + error ( "main", "invalid invocation of %s", PROGRAM_SHORT_NAME ); +#else + error ( "main", "invalid invocation of mmsrip" ); +#endif + else +#ifdef HAVE_VSNPRINTF + error ( "main", "output to \'%s\' is not attached to any stream", block->output ); +#else + error ( "main", "one of the -o output is not attached to any stream" ); +#endif + } + + ret = MMS_RET_ERROR; + goto clean; + } + + if ( block->output == NULL ) + { + char *tmp = strchr ( block->stream, '/' ); + char *interro_ptr = strchr ( block->stream, '?' ); + + if ( interro_ptr == NULL ) + output_filename = strrchr ( block->stream, '/' ); + else + { + do /* we look for the last '/' before the first '?' */ + { + output_filename = tmp; + tmp = strchr ( tmp+1, '/' ); + } + while ( ( tmp < interro_ptr ) && ( tmp != NULL ) ); + } + + if ( output_filename == NULL ) + { + if ( quiet < 2 ) +#ifdef HAVE_VSNPRINTF + error ( "main", "malformed url: \'%s\'", block->stream ); +#else + error ( "main", "malformed url" ); +#endif + + ret = MMS_RET_ERROR; + continue; + } + + ++output_filename; + + if ( strlen ( output_filename ) == 0 ) + { + if ( quiet < 2 ) +#ifdef HAVE_VSNPRINTF + error ( "main", "malformed url: \'%s\'", block->stream ); +#else + error ( "main", "malformed url" ); +#endif + + ret = MMS_RET_ERROR; + continue; + } + + block->output = (char *) strdup ( output_filename ); + + /* we clean filenames that look like 'stream.asf?digest=7Q2bjXo&provider=lala' */ + if ( ( interro_ptr = strchr(block->output,'?') ) != NULL ) + *interro_ptr = '\0'; + } + } + + if ( ret != MMS_RET_SUCCESS ) + goto clean; + + if ( delay != 0 ) + end = time(NULL) + delay; + + for ( block=stream_list; block!=NULL; block=(STREAM_LIST*)(retry?block:block->next) ) + { + output_filename = block->output; + retry = 0; + + if ( !test ) + { + if ( ( f = fopen ( output_filename, "w" ) ) == NULL ) + { + if ( quiet < 2 ) +#ifdef HAVE_VSNPRINTF + error ( "main", "unable to write in \'%s\'", output_filename ); +#else + error ( "main", "unable to write in output file" ); +#endif + + ret = MMS_RET_ERROR; + continue; + } + } + else + f = NULL; + + if ( ( mms = mms_create ( block->stream, f, stddebug, trick, test?1:(quiet>>1) ) ) == NULL ) + { + if ( quiet < 2 ) + error ( "main", "unable to create mms struct" ); + + if ( !test ) + { + fclose ( f ); + remove ( output_filename ); + } + + ret = MMS_RET_ERROR; + continue; + } + + if ( !quiet ) + fprintf ( stderr, "Connecting ...\r" ); + + if ( mms_connect ( mms ) != MMS_RET_SUCCESS ) + { + if ( quiet < 2 ) + error ( "main", "unable to connect" ); + + mms_destroy ( mms ); + + if ( !test ) + { + fclose ( f ); + remove ( output_filename ); + } + + ret = MMS_RET_ERROR; + continue; + } + + if ( !quiet ) + fprintf ( stderr, "Handshaking ...\r" ); + + if ( mms_handshake ( mms ) != MMS_RET_SUCCESS ) + { + if ( ( quiet < 2 ) && ( !test ) ) + error ( "main", "unable to handshake" ); + + mms_disconnect ( mms ); + mms_destroy ( mms ); + + if ( !test ) + { + fclose ( f ); + remove ( output_filename ); + } + + if ( !quiet ) + { + if ( !test ) + fprintf ( stderr, "Stream \'%s\' is not good.\n\n", block->stream ); + else + fprintf ( stderr, "Stream \'%s\' is not good.\n", block->stream ); + } + + ret = MMS_RET_ERROR; + continue; + } + + if ( test ) + { + if ( !quiet ) + fprintf ( stderr, "Stream \'%s\' is available.\n", block->stream ); + + mms_disconnect ( mms ); + mms_destroy ( mms ); + continue; + } + + if ( !quiet ) + fprintf ( stderr, "Getting header ...\r" ); + + if ( ( len_written = mms_write_stream_header ( mms ) ) == MMS_RET_ERROR ) + { + if ( quiet < 2 ) + error ( "main", "unable to write stream header" ); + + mms_disconnect ( mms ); + mms_destroy ( mms ); + fclose ( f ); + remove ( output_filename ); + ret = MMS_RET_ERROR; + continue; + } + + total_len_written = len_written; + + if ( !quiet ) + fprintf ( stderr, "Rip is about to start ...\r" ); + + if ( mms_begin_rip ( mms ) != MMS_RET_SUCCESS ) + { + if ( quiet < 2 ) + error ( "main", "unable to begin the rip" ); + + mms_disconnect ( mms ); + mms_destroy ( mms ); + fclose ( f ); + remove ( output_filename ); + ret = -1; + continue; + } + + if ( mms->is_live == MMS_LIVE ) + { + if ( !quiet ) + { + + fprintf ( stderr, " \r" ); + fprintf ( stderr, "%s: %d bytes written (--.- kbps)\r", output_filename, (ssize_t)total_len_written ); + gettimeofday ( &speed_last_update, NULL ); + } + + while ( 1 ) + { + len_written = mms_write_stream_data ( mms ); + + if ( len_written == 0 ) + break; + else if ( len_written == MMS_RET_ERROR ) + { + if ( quiet < 2 ) + error ( "main", "unable to write stream data" ); + + ret = MMS_RET_ERROR; + break; + } + else if ( len_written == MMS_RET_NO_AUTH ) + { + if ( trick == MMS_TRICK_DISABLED ) + { + /* we retry with the trick enabled */ + if ( quiet < 2 ) + fprintf ( stderr, "\r*** retrying with the anti-recalcitrant servers trick enabled ***\n" ); + + trick = MMS_TRICK_ENABLED; + retry = 1; + } + else + { + /* it's definitely not working */ + if ( quiet < 2 ) + error ( "main", "unable to write stream data" ); + + ret = MMS_RET_NO_AUTH; + } + + break; + } + + total_len_written += len_written; + + if ( !quiet ) + { + gettimeofday ( &now, NULL ); + + if ( now.tv_sec > speed_last_update.tv_sec ) + { + elapsed_time = (now.tv_sec - speed_last_update.tv_sec) + ((now.tv_usec - speed_last_update.tv_usec) / 1000000.0f); + + if ( elapsed_time >= 1.0f ) + { + speed = ( ( ((float) (total_len_written-old_total_len_written)) / elapsed_time) / 1024.0f ); + old_total_len_written = total_len_written; + gettimeofday ( &speed_last_update, NULL ); + } + } + + if ( speed > 0.0f ) + { + if ( (speed / 1024.0f) >= 1.0f ) + fprintf ( stderr, "%s: %d bytes written (%.1f mbps) \r", output_filename, (ssize_t)total_len_written, speed/1024.0f ); + else + fprintf ( stderr, "%s: %d bytes written (%.1f kbps) \r", output_filename, (ssize_t)total_len_written, speed ); + } + else + fprintf ( stderr, "%s: %d bytes written (--.- kbps) \r", output_filename, (ssize_t)total_len_written ); + } + + fflush ( f ); + + if ( delay != 0 ) + { + if ( end <= time(NULL) ) + { + delay = -1; + break; + } + } + } + + /* for a live, we should rewrite the header */ + + if ( ( ret == MMS_RET_SUCCESS ) && ( !quiet ) && ( !retry ) ) + fprintf ( stderr, "%s: %d bytes written \r", output_filename, (ssize_t)total_len_written ); + } + else + { + register const double coef = 100.0 / (double) mms->expected_file_size; + + if ( !quiet ) + { + fprintf ( stderr, " \r" ); + fprintf ( stderr, "%s: %.2f%% (--.- kbps)\r", output_filename, (double) total_len_written * coef ); + gettimeofday ( &speed_last_update, NULL ); + } + + while ( 1 ) + { + len_written = mms_write_stream_data ( mms ); + + if ( len_written == 0 ) + break; + else if ( len_written == MMS_RET_ERROR ) + { + if ( quiet < 2 ) + error ( "main", "unable to write stream data" ); + + ret = MMS_RET_ERROR; + break; + } + else if ( len_written == MMS_RET_NO_AUTH ) + { + if ( trick == MMS_TRICK_DISABLED ) + { + /* we retry with the trick enabled */ + if ( quiet < 2 ) + fprintf ( stderr, "\r*** retrying with the anti-recalcitrant servers trick enabled ***\n" ); + + trick = MMS_TRICK_ENABLED; + retry = 1; + } + else + { + /* it's definitely not working */ + if ( quiet < 2 ) + error ( "main", "unable to write stream data" ); + + ret = MMS_RET_NO_AUTH; + } + + break; + } + + total_len_written += len_written; + + if ( !quiet ) + { + gettimeofday ( &now, NULL ); + + if ( now.tv_sec > speed_last_update.tv_sec ) + { + elapsed_time = (now.tv_sec - speed_last_update.tv_sec) + ((now.tv_usec - speed_last_update.tv_usec) / 1000000.0f); + + if ( elapsed_time >= 1.0f ) + { + speed = ( ( ((float) (total_len_written-old_total_len_written)) / elapsed_time) / 1024.0f ); + old_total_len_written = total_len_written; + gettimeofday ( &speed_last_update, NULL ); + } + } + + if ( speed > 0.0f ) + { + if ( (speed / 1024.0f) >= 1.0f ) + fprintf ( stderr, "%s: %.2f%% (%.1f mbps) \r", output_filename, (double) total_len_written * coef, speed/1024.0f ); + else + fprintf ( stderr, "%s: %.2f%% (%.1f kbps) \r", output_filename, (double) total_len_written * coef, speed ); + } + else + fprintf ( stderr, "%s: %.2f%% (--.- kbps) \r", output_filename, (double) total_len_written * coef ); + } + + fflush ( f ); + + if ( delay != 0 ) + { + if ( end <= time(NULL) ) + { + delay = -1; + break; + } + } + } + + if ( ( ret == MMS_RET_SUCCESS ) && ( !quiet ) && ( delay != -1 ) && ( !retry ) ) + fprintf ( stderr, "%s: 100.00%% \r", output_filename ); + } + + mms_disconnect ( mms ); + mms_destroy ( mms ); + + fclose ( f ); + + if ( delay == -1 ) + break; + } + + clean: + if ( !quiet ) + fprintf ( stderr, "\n" ); + + if ( stddebug != NULL ) + { + fprintf ( stddebug, "\n\n--> debug log ends now\n" ); + fclose ( stddebug ); + } + + for ( block=stream_list; block!=NULL; ) + { + STREAM_LIST *old; + old = block; + block = (STREAM_LIST *) block->next; + + if ( old->stream != NULL ) + free ( old->stream ); + + if ( old->output != NULL ) + free ( old->output ); + + free ( old ); + } + + return (ret<0) ? (ret*-1) : ret; +} diff --git a/src/mmsrip/mms.c b/src/mmsrip/mms.c new file mode 100644 index 0000000..022f7ae --- /dev/null +++ b/src/mmsrip/mms.c @@ -0,0 +1,1239 @@ +/* + * $RCSfile: mms.c,v $ + * $Date: 2006/01/23 20:30:43 $ - $Revision: 1.33 $ + * + * This file is distributed as a part of MMSRIP ( MMS Ripper ). + * Copyright (c) 2005-2006 Nicolas BENOIT + * + * It is highly based on the work of SDP Multimedia and Major MMS. + * They deserve all the credits for it. + * + * This program is free software; you can 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#define _GNU_SOURCE + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <unistd.h> +#include <strings.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <errno.h> + +#include "common.h" +#include "mms.h" +#include "error.h" + + +/* + * Put 32 bits inside a command buffer. + */ +static void +mms_put_32 ( MMS_PACKET *pak, + uint32_t value ) +{ + pak->buf[pak->num_bytes ] = value % 256; + value = value >> 8; + pak->buf[pak->num_bytes+1] = value % 256 ; + value = value >> 8; + pak->buf[pak->num_bytes+2] = value % 256 ; + value = value >> 8; + pak->buf[pak->num_bytes+3] = value % 256 ; + + pak->num_bytes += 4; + return; +} + + +/* + * Returns the 32 bits located at specified offset. + */ +static uint32_t +mms_get_32 ( const unsigned char *buf, + const int offset ) +{ + return ( (((uint32_t)buf[offset+0]) << 0) | + (((uint32_t)buf[offset+1]) << 8) | + (((uint32_t)buf[offset+2]) << 16) | + (((uint32_t)buf[offset+3]) << 24) ); +} + + +/* + * Returns the 64 bits located at specified offset. + */ +static uint64_t +mms_get_64 ( const unsigned char *buf, + const int offset ) +{ + return ( (((uint64_t)buf[offset+0]) << 0) | + (((uint64_t)buf[offset+1]) << 8) | + (((uint64_t)buf[offset+2]) << 16) | + (((uint64_t)buf[offset+3]) << 24) | + (((uint64_t)buf[offset+4]) << 32) | + (((uint64_t)buf[offset+5]) << 40) | + (((uint64_t)buf[offset+6]) << 48) | + (((uint64_t)buf[offset+7]) << 56) ); +} + + +/* + * Converts a string into a UTF-16 string. + */ +static void +mms_string_utf16 ( uint8_t *dest, + unsigned char *src, + const ssize_t len ) +{ + ssize_t i; + memset ( dest, 0, len ); + + for ( i=0; i<len; ++i ) + { + dest[i*2] = src[i]; + dest[i*2+1] = 0; + } + + dest[i*2] = 0; + dest[i*2+1] = 0; + return; +} + + +/* + * Checks a URL for a correct MMS protocol. + * Returns the host name index if the protocol is valid, else it returns MMS_RET_ERROR. + */ +static int +mms_check_protocol ( const char *url ) +{ + static const char *proto[] = { "mms://", "mmsu://", "mmst://", "mmsh://", NULL }; + static const int proto_len[] = { 6, 7, 7, 7, 0 }; + register const char *p = proto[0]; + int i = 0; + + while ( p != NULL ) + { + if ( strncmp ( url, p, proto_len[i] ) == 0 ) + return proto_len[i]; + + p = proto[++i]; + } + + return MMS_RET_ERROR; +} + + +/* + * Prints out a command packet (debug purpose). + */ +static void +mms_print_packet ( FILE *stddebug, + const MMS_PACKET *pak, + const ssize_t length, + const int orig ) +{ + ssize_t i; + unsigned char c; + + fprintf ( stddebug, "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n" ); + + if ( orig == MMS_SERVER ) + fprintf ( stddebug, " command from server (%d bytes)\n", length ); + else + fprintf ( stddebug, " command from client (%d bytes)\n", length ); + + fprintf ( stddebug, " start sequence: 0x%08x\n", mms_get_32(pak->buf, 0) ); + fprintf ( stddebug, " command id: 0x%08x\n", mms_get_32(pak->buf, 4) ); + fprintf ( stddebug, " length: 0x%8x \n", mms_get_32(pak->buf, 8) ); + fprintf ( stddebug, " len8: 0x%8x \n", mms_get_32(pak->buf, 16) ); + fprintf ( stddebug, " sequence #: 0x%08x\n", mms_get_32(pak->buf, 20) ); + fprintf ( stddebug, " len8 (II): 0x%8x \n", mms_get_32(pak->buf, 32) ); + fprintf ( stddebug, " dir | comm: 0x%08x\n", mms_get_32(pak->buf, 36) ); + fprintf ( stddebug, " switches: 0x%08x\n", mms_get_32(pak->buf, 40) ); + + fprintf ( stddebug, "\nascii contents:\n" ); + + for ( i=48; i<length; i+=2 ) + { + c = pak->buf[i]; + + if ( ( c>=32 ) && ( c<=128 ) ) + fprintf ( stddebug, "%c", c ); + else + fprintf ( stddebug, "." ); + } + + fprintf ( stddebug, "\n\npackage hexdump:\n " ); + + i = 0; + + while ( 1 ) + { + c = pak->buf[i]; + fprintf ( stddebug, "%02x", c ); + + ++i; + + if ( i < length ) + { + if ( (i % 16) == 0 ) + fprintf ( stddebug, "\n" ); + + if ( (i % 2) == 0 ) + fprintf ( stddebug, " " ); + } + else + break; + } + + fprintf ( stddebug, "\n\n" ); + fflush ( stddebug ); + return; +} + + +/* + * Sends a command packet to the server. + * Returns MMS_RET_SUCCESS if success, MMS_RET_ERROR either. + */ +static int +mms_send_packet ( MMS *mms, + const int command, + const uint32_t switches, + const uint32_t extra, + const ssize_t length, + const uint8_t *data ) +{ + MMS_PACKET pak; + ssize_t written_len; + + const ssize_t len8 = ( length + (length%8) ) / 8; + + pak.num_bytes = 0; + + mms_put_32 ( &pak, 0x00000001 ); /* start sequence */ + mms_put_32 ( &pak, 0xB00BFACE ); /* #-)) */ + mms_put_32 ( &pak, length + 32 ); + mms_put_32 ( &pak, 0x20534d4d ); /* protocol type "MMS " */ + mms_put_32 ( &pak, len8 + 4 ); + mms_put_32 ( &pak, mms->seq_num++ ); + mms_put_32 ( &pak, 0x0 ); /* unknown */ + mms_put_32 ( &pak, 0x0 ); + mms_put_32 ( &pak, 2+len8 ); + mms_put_32 ( &pak, 0x00030000 | command ); /* dir | command */ + mms_put_32 ( &pak, switches ); + mms_put_32 ( &pak, extra ); + + memcpy ( &pak.buf[48], data, length ); + + written_len = write ( mms->socket, pak.buf, length+48 ); + + if ( written_len == -1 ) + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_send_packet", "write() said: %s", strerror(errno) ); +#else + error ( "mms_send_packet", "write() failed" ); +#endif + + return MMS_RET_ERROR; + } + else if ( written_len != (length+48) ) + { + if ( !mms->quiet ) + error ( "mms_send_packet", "did not write enough bytes" ); + + return MMS_RET_ERROR; + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, length+48, MMS_CLIENT ); + + return MMS_RET_SUCCESS; +} + + +/* + * Reads some bytes from the socket. + * This is blocking until we don't have the required amount of bytes. + * Returns MMS_RET_SUCCESS if success, MMS_RET_ERROR either. + */ +static int +mms_recv_packet ( const int s, + MMS_PACKET *pak, + size_t count, + const int quiet ) +{ + ssize_t read_len, total; + total = 0; + + if ( MMS_BUF_SIZE < count ) + { + if ( !quiet ) + warning ( "mms_recv_packet", "caller is too greedy" ); + + count = MMS_BUF_SIZE; + } + + while ( total < count ) + { + read_len = read ( s, &pak->buf[total], count-total ); + + if ( read_len == -1 ) + { + if ( !quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_recv_packet", "read() said: %s", strerror(errno) ); +#else + error ( "mms_recv_packet", "read() failed" ); +#endif + + return MMS_RET_ERROR; + } + + total += read_len; + } + + return MMS_RET_SUCCESS; +} + + +/* + * Gets server's command. + * The first 8 bytes can be skipped setting offset to 8. + * It writes the packet length in the provided storage. + * Returns command ID, MMS_CMD_INVALID if error. + */ +static int +mms_recv_cmd_packet ( const int s, + MMS_PACKET *pak, + ssize_t *packet_len, + const int offset, + const int quiet ) +{ + MMS_PACKET tmp; + + if ( ( offset > 8 ) || ( offset < 0 ) ) + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "provided offset is invalid" ); + + return MMS_RET_ERROR; + } + + if ( mms_recv_packet ( s, &tmp, 12-offset, quiet ) != 0 ) + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "unable to get packet header" ); + + return MMS_RET_ERROR; + } + + memcpy ( &pak->buf[offset], tmp.buf, 12-offset ); + + if ( offset == 0 ) + { + if ( mms_get_32(pak->buf, 4) != 0xb00bface ) /* probably a coincidence... */ + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "answer should have been a cmd packet" ); + + return MMS_RET_ERROR; + } + } + + memcpy ( packet_len, &pak->buf[8], 4 ); + *packet_len = mms_get_32 ( (unsigned char *) packet_len, 0 ) + 4; + + if ( ( *packet_len + 12 ) > MMS_BUF_SIZE ) + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "incoming packet is too big for me" ); + + return MMS_RET_ERROR; + } + + if ( mms_recv_packet ( s, &tmp, *packet_len, quiet ) != 0 ) + { + if ( !quiet ) + error ( "mms_recv_cmd_packet", "unable to get packet body" ); + + return MMS_RET_ERROR; + } + + memcpy ( &pak->buf[12], tmp.buf, *packet_len ); + + return ( mms_get_32(pak->buf, 36) & 0xFFFF ); +} + + +/* + * Gets the header of the ASF/WMV stream/file. + * Returns the amount of bytes received, MMS_RET_ERROR if we encounter an error. + */ +static ssize_t +mms_recv_header_packet ( MMS *mms, + MMS_PACKET *pak ) +{ + MMS_PACKET pre_header; + MMS_PACKET tmp; + ssize_t header_len; + ssize_t packet_len; + + header_len = 0; + + while ( 1 ) + { + if ( mms_recv_packet ( mms->socket, &pre_header, 8, mms->quiet ) != 0 ) + { + if ( !mms->quiet ) + error ( "mms_recv_header_packet", "unable to get pre-header" ); + + return MMS_RET_ERROR; + } + + if ( pre_header.buf[4] == 0x02 ) + { + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n" ); + fprintf ( mms->stddebug, " getting header packet from server\n\n" ); + } + + if ( mms->stddebug != NULL ) + { + static int i; /* static, so we don't recreate it for each call */ + + for ( i=0; i<8; ++i ) + fprintf ( mms->stddebug, "pre_header.buf[%d] = 0x%02x (%d)\n", i, pre_header.buf[i], pre_header.buf[i] ); + } + + packet_len = ( pre_header.buf[7] << 8 | pre_header.buf[6] ) - 8; + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "\nASF Header Packet (%d bytes)\n", packet_len ); + + if ( mms_recv_packet ( mms->socket, &tmp, packet_len, mms->quiet ) != 0 ) + { + if ( !mms->quiet ) + error ( "mms_recv_header_packet", "unable to get header" ); + + return MMS_RET_ERROR; + } + + if ( ( header_len + packet_len ) > MMS_BUF_SIZE ) + { + if ( !mms->quiet ) + error ( "mms_recv_header_packet", "total header length is too big for me" ); + + return MMS_RET_ERROR; + } + + memcpy ( &pak->buf[header_len], tmp.buf, packet_len ); + + header_len += packet_len; + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "\n" ); + + /* test if end of header is reached */ + if ( ( pak->buf[header_len-1] == 1) && + ( pak->buf[header_len-2] == 1 ) ) + return header_len; + } + else /* we might receive an ack or an error */ + { + int command = mms_recv_cmd_packet ( mms->socket, &tmp, &packet_len, 8, mms->quiet ); + + if ( command == -1 ) + { + if ( !mms->quiet ) + error ( "mms_recv_header_packet", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + memcpy ( tmp.buf, pre_header.buf, 8 ); + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &tmp, packet_len, MMS_SERVER ); + + if ( command == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, tmp.buf ); + else if ( command != MMS_CMD_HEADER_DATA ) + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_recv_header_packet", "unknown command 0x%02x\n", command ); +#else + error ( "mms_recv_header_packet", "unknown command\n" ); +#endif + + return MMS_RET_ERROR; + } + } + } +} + + +/* + * Interprates the header (media packet size, etc.). + * Returns the size of a media packet (should be != 0 ). + */ +static ssize_t +mms_interp_header ( MMS *mms, + const uint8_t *header, + const ssize_t header_len ) +{ + int i; + ssize_t packet_length = 0; + + mms->expected_file_size = header_len; + + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n" ); + fprintf ( mms->stddebug, " header interpretation\n\n" ); + } + + /* parse header */ + mms->num_stream_ids = 0; + i = 30; + + while ( i < header_len ) + { + uint64_t guid_1, guid_2, length; + + if ( mms->stddebug != NULL ) + { + /* this is MS GUID format */ + fprintf ( mms->stddebug, "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n", + header[i+3], header[i+2], header[i+1], header[i+0], + header[i+5], header[i+4], header[i+7], header[i+6], + header[i+8], header[i+9], header[i+10], header[i+11], + header[i+12], header[i+13], header[i+14], header[i+15] ); + } + + guid_2 = mms_get_64 ( header, i ); + i += 8; + + guid_1 = mms_get_64 ( header, i ); + i += 8; + + length = mms_get_64 ( header, i ); + i += 8; + + if ( ( guid_1 == 0x6553200cc000e48eULL ) && ( guid_2 == 0x11cfa9478cabdca1ULL ) ) + { + packet_length = mms_get_32 ( header, i+92-24 ); + + if ( packet_length != mms_get_32(header, i+72) ) + warning ( NULL, "size of media packets is not constant, it should not happen" ); + + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "File Properties Object (%lld bytes)\n", length ); + fprintf ( mms->stddebug, " -> broadcast bit: %d\n", mms_get_32(header,i+64)&1 ); + fprintf ( mms->stddebug, " -> min packet length: %d\n", packet_length ); + fprintf ( mms->stddebug, " -> max packet length: %d\n", mms_get_32(header, i+72) ); + fprintf ( mms->stddebug, " -> number of media packets: %d\n\n", mms_get_32(header, i+32) ); + } + + if ( mms_get_32(header,i+64)&1 ) + { + if ( ( !mms->quiet ) && ( !mms->is_live ) ) + warning ( NULL, "stream seems to be live, an error may occur" ); + + mms->is_live = MMS_LIVE; + } + } + else if ( ( guid_1 == 0x6553200cc000e68eULL ) && ( guid_2 == 0x11cfa9b7b7dc0791ULL ) ) + { + int stream_id = header[i+48] | header[i+49] << 8; + + if ( mms->num_stream_ids < 20 ) + { + mms->stream_ids[mms->num_stream_ids] = stream_id; + ++mms->num_stream_ids; + } + else + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + warning ( "mms_interp_header", "too many streams, stream \'%d\' skipped", stream_id ); +#else + warning ( "mms_interp_header", "too many streams, skipping.." ); +#endif + } + + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "Stream Object (%lld bytes)\n", length ); + fprintf ( mms->stddebug, " -> stream id: %d\n\n", stream_id ); + } + } + else if ( ( guid_1 == 0x6cce6200aa00d9a6ULL ) && ( guid_2 == 0x11cf668e75b22636ULL ) ) + { + mms->expected_file_size += ( length - 50 ); /* valid values are at least 50 bytes (why?) */ + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "Data Object (%lld bytes)\n\n", length ); + } + else if ( mms->stddebug != NULL ) + { + if ( ( guid_1 == 0x6cce6200aa00d9a6ULL ) && ( guid_2 == 0x11cf668e75b22630ULL ) ) + fprintf ( mms->stddebug, "Header Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0xcb4903c9a000f489ULL ) && ( guid_2 == 0x11cfe5b133000890ULL) ) + fprintf ( mms->stddebug, "Simple Index Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0xbe4903c9a0003490ULL ) && ( guid_2 == 0x11d135dad6e229d3ULL) ) + fprintf ( mms->stddebug, "Index Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0x6553200cc000e38eULL ) && ( guid_2 == 0x11cfa92e5fbf03b5ULL) ) + fprintf ( mms->stddebug, "Header Extension Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0x6cce6200aa00d9a6ULL ) && ( guid_2 == 0x11cf668e75b22633ULL) ) + fprintf ( mms->stddebug, "Content Description Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0x50a85ec9a000f097ULL ) && ( guid_2 == 0x11d2e307d2d0a440ULL) ) + fprintf ( mms->stddebug, "Extended Content Description Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0xf64803c9a000a4a3ULL ) && ( guid_2 == 0x11d0311d86d15240ULL) ) + fprintf ( mms->stddebug, "Codec List Object (%lld bytes)\n\n", length ); + else if ( ( guid_1 == 0xb2a2c9976000828dULL ) && ( guid_2 == 0x11d1468d7bf875ceULL) ) + fprintf ( mms->stddebug, "Stream Bitrate Properties Object (%lld bytes)\n\n", length ); + else + fprintf ( mms->stddebug, "Unknown Object (%lld bytes)\n\n", length ); + } + + i += length-24; + } + + return packet_length; +} + + +/* + * Gets a media packet. + * Returns the amount of bytes in the media packet (0 if EOF), MMS_RET_ACKED if ACKed, + * MMS_RET_NO_AUTH if authorization failed, MMS_RET_ERROR either. + */ +static ssize_t +mms_recv_media_packet ( MMS *mms, + MMS_PACKET *pak ) +{ + MMS_PACKET pre_header; + ssize_t packet_len; + + if ( mms_recv_packet ( mms->socket, &pre_header, 8, mms->quiet ) != 0 ) + { + if ( !mms->quiet ) + error ( "mms_recv_media_packet", "unable to get pre-header" ); + + return MMS_RET_ERROR; + } + + if ( pre_header.buf[4] == 0x04 ) + { + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n" ); + fprintf ( mms->stddebug, " getting media packet from server\n\n" ); + } + + if ( mms->stddebug != NULL ) + { + static int i; /* static, so we don't recreate it for each call */ + + for ( i=0; i<8; i++ ) + fprintf ( mms->stddebug, "pre_header[%d] = 0x%02x (%d)\n", i, pre_header.buf[i], pre_header.buf[i] ); + } + + packet_len = (pre_header.buf[7] << 8 | pre_header.buf[6]) - 8; + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "\nASF Media Packet (%d bytes)\n", packet_len ); + + memset ( pak->buf, 0, mms->media_packet_len ); + + if ( mms_recv_packet (mms->socket, pak, packet_len, mms->quiet) != 0 ) + { + if ( !mms->quiet ) + error ( "mms_recv_media_packet", "unable to get media packet" ); + + return MMS_RET_ERROR; + } + } + else + { + int command = mms_recv_cmd_packet ( mms->socket, pak, &packet_len, 8, mms->quiet ); + + if ( command == MMS_RET_ERROR ) + { + if ( !mms->quiet ) + error ( "mms_recv_media_packet", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + memcpy ( pak->buf, pre_header.buf, 8 ); + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, pak, packet_len, MMS_SERVER ); + + if ( command == MMS_CMD_PING ) + { + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak->buf ); + return MMS_RET_ACKED; + } + else if ( command == MMS_CMD_END_OF_STREAM ) + return 0; + else if ( command == MMS_CMD_STREAM_SELECT_ACK ) /* it happens sometimes */ + return MMS_RET_ACKED; + else if ( command == MMS_CMD_READY_TO_STREAM ) /* it always happen before the first media packet */ + { + /* this happens on some server for some unknown reason */ + if ( mms_get_32(pak->buf, 40) == 0x80070005 ) + { + if ( !mms->quiet ) + error ( "mms_recv_media_packet", "streaming denied (read manpage & retry later)" ); + + return MMS_RET_NO_AUTH; + } + + return MMS_RET_ACKED; + } + else + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_recv_media_packet", "unknown command 0x%02x\n", command ); +#else + error ( "mms_recv_media_packet", "unknown command\n" ); +#endif + + return MMS_RET_ERROR; + } + } + + if ( mms->stddebug != NULL ) + fprintf ( mms->stddebug, "\n" ); + + return packet_len; +} + + +/* + * Creates An MMS Struct + * Returns an initialized MMS Struct if successful, NULL if an error occurs. + * url must start with mms:// or mmst:// (mmsu:// and mmsh:// are OK too, but we won't use UDP or do MMS over HTTP) + * trick must be MMS_TRICK_DISABLED or MMS_TRICK_ENABLED + */ +MMS * +mms_create ( const char *url, + FILE *out, + FILE *stddebug, + const int trick, + const int quiet ) +{ + MMS *mms; + register const char *sep; + register const int host_idx = mms_check_protocol ( url ); + register const int url_len = strlen ( url ); + + if ( stddebug != NULL ) + { + fprintf ( stddebug, "\n\n********************************************************************************\n\n" ); + fprintf ( stddebug, "Url -> \'%s\'\n", url ); + } + + if ( host_idx == MMS_RET_ERROR ) + { + if ( !quiet ) + error ( "mms_create", "bad protocol (mms:// was expected)" ); + + return NULL; + } + + sep = strchr ( &url[host_idx], '/' ); + + if ( sep == NULL ) + { + if ( !quiet ) + error ( "mms_create", "url seems to be malformed" ); + + return NULL; + } + + if ( ( mms = ( MMS * ) malloc ( sizeof(MMS) ) ) == NULL ) + { + if ( !quiet ) + error ( "mms_create", "unable to allocate memory" ); + + return NULL; + } + + mms->host = malloc ( ( sep - &url[host_idx] ) + 1 ); + strncpy ( mms->host, &url[host_idx], (sep - &url[host_idx]) ); + mms->host[sep-&url[host_idx]] = '\0'; /* strndup */ + + mms->path = strdup ( sep+1 ); + mms->out = out; + mms->seq_num = 0; + mms->expected_file_size = 0; + mms->is_live = MMS_NO_LIVE; + + /* we try to guess stream type with the filename extension */ + if ( ( sep = strchr(sep,'?') ) != NULL ) + { + if ( ( *(sep-4) == '.' ) && /* this will never segfault because */ + ( *(sep-3) == 'w' ) && /* the url is at least 6 chars long (mms://) */ + ( *(sep-2) == 'm' ) && + ( *(sep-1) == 'v' ) ) + mms->stream_type = MMS_WMV; + else + mms->stream_type = MMS_ASF; + } + else + { + if ( ( url[url_len-4] == '.' ) && + ( url[url_len-3] == 'w' ) && + ( url[url_len-2] == 'm' ) && + ( url[url_len-1] == 'v' ) ) + mms->stream_type = MMS_WMV; + else + mms->stream_type = MMS_ASF; + } + + mms->stddebug = stddebug; + mms->quiet = quiet; + mms->trick = ( ((trick==MMS_TRICK_DISABLED)||(trick==MMS_TRICK_ENABLED)) ? trick : MMS_TRICK_DISABLED ); + + if ( mms->stddebug != NULL ) + { + fprintf ( mms->stddebug, "Host -> \'%s\'\nPath -> \'%s\'\n", mms->host, mms->path ); + fprintf ( mms->stddebug, "Stream type -> %s\n", (mms->stream_type==MMS_WMV)?"MMS_WMV":"MMS_ASF" ); + } + + return mms; +} + + +/* + * Establishes a connection with the MMS server. + * Returns MMS_RET_SUCCESS if success, MMS_RET_ERROR either. + */ +int +mms_connect ( MMS* mms ) +{ + struct sockaddr_in sa; + struct hostent *hp; + + if ( mms == NULL ) + return -1; + + if ( ( hp = gethostbyname(mms->host) ) == NULL ) + { + if ( !mms->quiet ) + error ( "mms_connect", "unable to resolve host name" ); + + return MMS_RET_ERROR; + } + + bcopy ( (char *) hp->h_addr, (char *) &sa.sin_addr, hp->h_length ); + sa.sin_family = hp->h_addrtype; + sa.sin_port = htons ( 1755 ); + + if ( ( mms->socket = socket(hp->h_addrtype, SOCK_STREAM, 0) ) == -1 ) + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_connect", "socket() said: %s", strerror(errno) ); +#else + error ( "mms_connect", "socket() failed" ); +#endif + + return MMS_RET_ERROR; + } + + if ( connect ( mms->socket, (struct sockaddr *) &sa, sizeof sa ) != 0 ) + { + if ( !mms->quiet ) +#ifdef HAVE_VSNPRINTF + error ( "mms_connect", "connect() said: %s", strerror(errno) ); +#else + error ( "mms_connect", "connect failed" ); +#endif + + return MMS_RET_ERROR; + } + + return MMS_RET_SUCCESS; +} + + +/* + * Handshake with the MMS server. + * ( we don't care about server's answers ) + */ +int +mms_handshake ( MMS *mms ) +{ + MMS_PACKET pak; + int cmd = MMS_CMD_PING; + ssize_t packet_len; + char str [ 1024 ]; + uint8_t data [ 2092 ]; + + if ( mms == NULL ) + return MMS_RET_ERROR; + + /* who we are */ + memset ( data, 0, sizeof (data) ); + + if ( mms->trick == MMS_TRICK_DISABLED ) + { +#ifdef HAVE_SNPRINTF + snprintf ( str, sizeof(str), "\034\003NSPlayer/7.0.0.1956; {3300AD50-2C39-46c0-AE0A-60181587CBA}; Host: %s", mms->host ); +#else + if ( (strlen("\034\003NSPlayer/7.0.0.1956; {3300AD50-2C39-46c0-AE0A-60181587CBA}; Host: ")+strlen(mms->host)) < sizeof(str) ) + sprintf ( str, "\034\003NSPlayer/7.0.0.1956; {3300AD50-2C39-46c0-AE0A-60181587CBA}; Host: %s", mms->host ); + else + { + error ( "mms_handshake", "host name is too long" ); + return MMS_RET_ERROR; + } +#endif + } + else + strcpy ( str, "\034\003NSPlayer/4.1.0.3928; {3300AD50-2C39-46c0-AE0A-60181587CBA}" ); + + mms_string_utf16 ( data, (unsigned char *) str, strlen(str)+2 ); + mms_send_packet ( mms, MMS_CMD_HELLO, 0, 0x0004000b, strlen(str)*2 + 6, data ); + + cmd = MMS_CMD_PING; + while ( cmd == MMS_CMD_PING ) + { + cmd = mms_recv_cmd_packet ( mms->socket, &pak, &packet_len, 0, mms->quiet ); + + if ( cmd == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak.buf ); + } + + if ( cmd == MMS_CMD_INVALID ) + { + if ( !mms->quiet ) + error ( "mms_handshake", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, packet_len, MMS_SERVER ); + + /* transport protocol selection */ + memset ( &data, 0, sizeof(data) ); + mms_string_utf16 ( &data[8], (unsigned char *) "\002\000\\\\192.168.0.129\\TCP\\1037\0000", 28 ); + mms_send_packet ( mms, MMS_CMD_PROTOCOL_SELECT, 0, 0, 28*2+8, data ); + + cmd = MMS_CMD_PING; + while ( cmd == MMS_CMD_PING ) + { + cmd = mms_recv_cmd_packet ( mms->socket, &pak, &packet_len, 0, mms->quiet ); + + if ( cmd == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak.buf ); + } + + if ( cmd == MMS_CMD_INVALID ) + { + if ( !mms->quiet ) + error ( "mms_handshake", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, packet_len, MMS_SERVER ); + + /* requested file */ + mms_string_utf16 ( &data[8], (unsigned char *) mms->path, strlen(mms->path) ); + memset ( data, 0, 8 ); + mms_send_packet ( mms, MMS_CMD_FILE_REQUEST, 0, 0, strlen(mms->path)*2+12, data ); + + cmd = MMS_CMD_PING; + while ( cmd == MMS_CMD_PING ) + { + cmd = mms_recv_cmd_packet ( mms->socket, &pak, &packet_len, 0, mms->quiet ); + + if ( cmd == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak.buf ); + } + + if ( cmd == MMS_CMD_INVALID ) + { + if ( !mms->quiet ) + error ( "mms_handshake", "unable to get cmd packet" ); + + return MMS_RET_ERROR; + } + + if ( cmd == MMS_CMD_STREAM_INFOS ) + { + if ( mms_get_32 ( pak.buf, 48 ) == -1 ) + { + if ( !mms->quiet ) + error ( "mms_handshake", "stream infos are not available" ); + + return MMS_RET_ERROR; + } + + if ( ( mms_get_32(pak.buf,72) == 0 ) || ( mms_get_32(pak.buf,72) == 0xFFFFFFFF ) ) + { + mms->is_live = MMS_LIVE; + + if ( !mms->quiet ) + warning ( NULL, "stream seems to be live, an error may occur" ); + } + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, packet_len, MMS_SERVER ); + + return MMS_RET_SUCCESS; +} + + +/* + * Gets ASF/WMV stream/file header + */ +ssize_t +mms_write_stream_header ( MMS *mms ) +{ + MMS_PACKET pak; + ssize_t len; + + if ( mms == NULL ) + return MMS_RET_ERROR; + + /* give me the stream header please... */ + memset ( pak.buf, 0, 40 ); + pak.buf[32] = 2; + mms_send_packet ( mms, MMS_CMD_HEADER_REQUEST, 1, 0, 40, pak.buf ); + + len = mms_recv_header_packet ( mms, &pak ); + + if ( len == -1 ) + { + if ( !mms->quiet ) + error ( "mms_get_stream_header", "unable to get stream header\n" ); + + return MMS_RET_ERROR; + } + else if ( len == 0 ) + { + if ( !mms->quiet ) + error ( "mms_get_stream_header", "no stream header available, file may not exist" ); + + return MMS_RET_ERROR; + } + + fwrite ( pak.buf, len, 1, mms->out ); + + mms->media_packet_len = mms_interp_header ( mms, pak.buf, len ); + + if ( mms->media_packet_len > MMS_BUF_SIZE ) + { + if ( !mms->quiet ) + error ( "mms_get_stream_header", "media packet length is too big for me" ); + + return MMS_RET_ERROR; + } + + return len; +} + + +/* + * Tells the server we're ready to rip the stream from the beginning (don't know what happens if it's a live) + * Returns 0 if success, -1 either. + */ +int +mms_begin_rip ( MMS *mms ) +{ + int i; + uint8_t data [ 1024 ]; + MMS_PACKET pak; + ssize_t packet_len; + int cmd; + + if ( mms == NULL ) + return MMS_RET_ERROR; + + /* media stream selection */ + memset ( data, 0, 40 ); + + for ( i=1; i<mms->num_stream_ids; ++i ) + { + data [ (i-1) * 6 + 2 ] = 0xFF; + data [ (i-1) * 6 + 3 ] = 0xFF; + data [ (i-1) * 6 + 4 ] = mms->stream_ids[i]; + data [ (i-1) * 6 + 5 ] = 0x00; + } + + /* we add the final bytes of the stream selector, necessary for ASF streams only */ + if ( mms->stream_type == MMS_ASF ) + { + data [ (mms->num_stream_ids-1) * 6 + 0 ] = 0x00; + data [ (mms->num_stream_ids-1) * 6 + 1 ] = 0x00; + data [ (mms->num_stream_ids-1) * 6 + 2 ] = 0x00; + data [ (mms->num_stream_ids-1) * 6 + 3 ] = 0x20; + data [ (mms->num_stream_ids-1) * 6 + 4 ] = 0xac; + data [ (mms->num_stream_ids-1) * 6 + 5 ] = 0x40; + data [ (mms->num_stream_ids-1) * 6 + 6 ] = 0x02; + + mms_send_packet ( mms, MMS_CMD_STREAM_SELECT, mms->num_stream_ids, 0xFFFF | mms->stream_ids[0] << 16, (mms->num_stream_ids-1)*6+2+4, data ); + } + else + { + mms_send_packet ( mms, MMS_CMD_STREAM_SELECT, mms->num_stream_ids, 0xFFFF | mms->stream_ids[0] << 16, (mms->num_stream_ids-1)*6+2, data ); + } + + cmd = MMS_CMD_PING; + while ( cmd == MMS_CMD_PING ) + { + cmd = mms_recv_cmd_packet ( mms->socket, &pak, &packet_len, 0, mms->quiet ); + + if ( cmd == MMS_CMD_PING ) + mms_send_packet ( mms, MMS_CMD_PONG, 0, 0, 0, pak.buf ); + } + + if ( cmd == MMS_CMD_INVALID ) + { + if ( !mms->quiet ) + error ( "mms_begin_rip", "unable to get server\'s confirmation" ); + + return MMS_RET_ERROR; + } + + if ( mms->stddebug != NULL ) + mms_print_packet ( mms->stddebug, &pak, packet_len, MMS_SERVER ); + + + /* ready... */ + memset ( data, 0, 40 ); + + for ( i=8; i<16; ++i ) + data[i] = 0xFF; + + data[20] = 0x04; + + mms_send_packet ( mms, MMS_CMD_START_PACKET, 1, 0xFFFF | mms->stream_ids[0] << 16, 24, data ); + + return MMS_RET_SUCCESS; +} + + +/* + * Writes a media packet. + * Returns the amount of bytes written, 0 if EOF, MMS_RET_NO_AUTH if streaming is denied, MMS_RET_ERROR either. + */ +ssize_t +mms_write_stream_data ( MMS *mms ) +{ + ssize_t len; + MMS_PACKET pak; + + if ( mms == NULL ) + return 0; + + do + { + len = mms_recv_media_packet ( mms, &pak ); + } + while ( len == MMS_RET_ACKED ); /* while we receive ACK packets */ + + if ( len == 0 ) + return 0; + else if ( len == -1 ) + { + if ( !mms->quiet ) + error ( "mms_write_stream_data", "mms_recv_media_packet failed" ); + + return MMS_RET_ERROR; + } + else if ( len == MMS_RET_NO_AUTH ) + { + if ( !mms->quiet ) + error ( "mms_write_stream_data", "mms_recv_media_packet failed" ); + + return MMS_RET_NO_AUTH; + } + + fwrite ( pak.buf, mms->media_packet_len, 1, mms->out ); /* we might have read less than the arbitrary media packet size */ + + return mms->media_packet_len; +} + + +/* + * Closes the connection. + */ +void +mms_disconnect ( MMS *mms ) +{ + uint8_t data [ 1024 ]; + + if ( mms == NULL ) + return; + + mms_send_packet ( mms, MMS_CMD_BYE_BYE, 0, 0, 0, data ); + + if ( ( shutdown(mms->socket, SHUT_RDWR) | close(mms->socket)) != 0 ) + if ( !mms->quiet ) + warning ( "mms_disconnect", "unable to close the socket properly" ); + + return; +} + + +/* + * Destroys An MMS Struct + */ +void +mms_destroy ( MMS *mms ) +{ + if ( mms == NULL ) + return; + + if ( mms->host != NULL ) + free ( mms->host ); + + if ( mms->path != NULL ) + free ( mms->path ); + + free ( mms ); + + return; +} diff --git a/src/mmsrip/mms.h b/src/mmsrip/mms.h new file mode 100644 index 0000000..600d7bd --- /dev/null +++ b/src/mmsrip/mms.h @@ -0,0 +1,129 @@ +/* + * $RCSfile: mms.h,v $ + * $Date: 2006/01/23 20:30:43 $ - $Revision: 1.17 $ + * + * This file is distributed as a part of MMSRIP ( MMS Ripper ). + * Copyright (c) 2005-2006 Nicolas BENOIT + * + * It is highly based on the work of SDP Multimedia and Major MMS. + * They deserve all the credits for it. + * + * This program is free software; you can 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, 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, 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#ifndef __MMS_H__ +#define __MMS_H__ + +#ifdef _WIN32 +typedef signed long int ssize_t; +#define bcopy(s, d, l) memcpy(d, s, l) +#include <assert.h> +#define close closesocket +#define read(soc, data, len) recv(soc, data, len, 0) +#define write(soc, data, len) send(soc, data, len, 0) +#define SHUT_RDWR SD_BOTH +#endif + +#if defined(__CYGWIN__) || defined(_WIN32) +typedef unsigned char uint8_t; +#ifndef __uint32_t_defined +#define __uint32_t_defined +typedef unsigned int uint32_t; +#endif +typedef unsigned long long int uint64_t; +#else +#if defined(SOLARIS) || defined(sun) +#include <inttypes.h> +#else +#include <stdint.h> +#endif +#endif + +#define MMS_SERVER 0 +#define MMS_CLIENT 1 + +#define MMS_NO_LIVE 0 +#define MMS_LIVE 1 + +#define MMS_WMV 0 +#define MMS_ASF 1 + +#define MMS_TRICK_DISABLED 0 +#define MMS_TRICK_ENABLED 1 + +#define MMS_CMD_INVALID -1 +#define MMS_CMD_HELLO 0x01 +#define MMS_CMD_PROTOCOL_SELECT 0x02 +#define MMS_CMD_FILE_REQUEST 0x05 +#define MMS_CMD_READY_TO_STREAM 0x05 +#define MMS_CMD_STREAM_INFOS 0x06 +#define MMS_CMD_START_PACKET 0x07 +#define MMS_CMD_STOP_STREAM 0x09 +#define MMS_CMD_BYE_BYE 0x0D +#define MMS_CMD_HEADER_DATA 0x11 +#define MMS_CMD_HEADER_REQUEST 0x15 +#define MMS_CMD_NET_TESTING 0x15 +#define MMS_CMD_PING 0x1B +#define MMS_CMD_PONG 0x1B +#define MMS_CMD_END_OF_STREAM 0x1E +#define MMS_CMD_STREAM_SELECT_ACK 0x21 +#define MMS_CMD_STREAM_SELECT 0x33 + +#define MMS_RET_SUCCESS 0 +#define MMS_RET_ERROR -1 +#define MMS_RET_NO_AUTH -2 +#define MMS_RET_ACKED -3 + + +#define MMS_BUF_SIZE 102400 + +typedef struct +{ + uint8_t buf[MMS_BUF_SIZE]; + int num_bytes; +} MMS_PACKET ; + + +typedef struct +{ + char *host; + char *path; + int socket; + FILE *out; + FILE *stddebug; + ssize_t media_packet_len; + uint64_t expected_file_size; + int is_live; + int stream_type; + int seq_num; + int num_stream_ids; + int stream_ids[20]; + int quiet; + int trick; +} MMS ; + + +MMS * mms_create ( const char *, FILE *, FILE *, const int, const int ); +int mms_connect ( MMS* ); +int mms_handshake ( MMS * ); +ssize_t mms_write_stream_header ( MMS * ); +int mms_begin_rip ( MMS * ); +ssize_t mms_write_stream_data ( MMS * ); +void mms_disconnect ( MMS * ); +void mms_destroy ( MMS * ); + +#endif |