diff options
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/mms/Makefile.am | 15 | ||||
-rw-r--r-- | plugins/mms/libmms/AUTHORS | 6 | ||||
-rw-r--r-- | plugins/mms/libmms/COPYING.LIB | 510 | ||||
-rw-r--r-- | plugins/mms/libmms/README | 57 | ||||
-rw-r--r-- | plugins/mms/libmms/README.LICENSE | 6 | ||||
-rw-r--r-- | plugins/mms/libmms/asfheader.h | 265 | ||||
-rw-r--r-- | plugins/mms/libmms/bswap.h | 280 | ||||
-rw-r--r-- | plugins/mms/libmms/mms-common.h | 35 | ||||
-rw-r--r-- | plugins/mms/libmms/mms.c | 1802 | ||||
-rw-r--r-- | plugins/mms/libmms/mms.h | 80 | ||||
-rw-r--r-- | plugins/mms/libmms/mmsh.c | 1534 | ||||
-rw-r--r-- | plugins/mms/libmms/mmsh.h | 63 | ||||
-rw-r--r-- | plugins/mms/libmms/mmsio.h | 93 | ||||
-rw-r--r-- | plugins/mms/libmms/mmsx.c | 168 | ||||
-rw-r--r-- | plugins/mms/libmms/mmsx.h | 69 | ||||
-rw-r--r-- | plugins/mms/libmms/uri.c | 1033 | ||||
-rw-r--r-- | plugins/mms/libmms/uri.h | 92 | ||||
-rw-r--r-- | plugins/mms/mmsplug.c (renamed from plugins/mms/mms.c) | 0 |
18 files changed, 6107 insertions, 1 deletions
diff --git a/plugins/mms/Makefile.am b/plugins/mms/Makefile.am index c6a06fdb..f16f7412 100644 --- a/plugins/mms/Makefile.am +++ b/plugins/mms/Makefile.am @@ -1,7 +1,20 @@ if HAVE_MMS mmsdir = $(libdir)/$(PACKAGE) pkglib_LTLIBRARIES = mms.la -mms_la_SOURCES = mms.c +mms_la_SOURCES = mmsplug.c\ + libmms/mms.c\ + libmms/mmsh.c\ + libmms/mmsx.c\ + libmms/uri.c\ + libmms/asfheader.h\ + libmms/bswap.h\ + libmms/mms-common.h\ + libmms/mms.h\ + libmms/mmsh.h\ + libmms/mmsio.h\ + libmms/mmsx.h\ + libmms/uri.h + mms_la_LDFLAGS = -module mms_la_LIBADD = $(LDADD) $(LIBMMS_LIBS) diff --git a/plugins/mms/libmms/AUTHORS b/plugins/mms/libmms/AUTHORS new file mode 100644 index 00000000..04a68431 --- /dev/null +++ b/plugins/mms/libmms/AUTHORS @@ -0,0 +1,6 @@ +Original author of the MMS interface code was Major MMS of http://www.geocities.com/majormms/ +Enhanced and maintained by Xine project at http://xine.sf.net + +Current developers of libmms are: +Maciej Katafiasz (Mathrick) <mathrick@users.sourceforge.net> +Søren Hansen (shawarma) <sh@warma.dk> diff --git a/plugins/mms/libmms/COPYING.LIB b/plugins/mms/libmms/COPYING.LIB new file mode 100644 index 00000000..b124cf58 --- /dev/null +++ b/plugins/mms/libmms/COPYING.LIB @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control +compilation and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply, and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License +may add an explicit geographical distribution limitation excluding those +countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/plugins/mms/libmms/README b/plugins/mms/libmms/README new file mode 100644 index 00000000..8426d2cf --- /dev/null +++ b/plugins/mms/libmms/README @@ -0,0 +1,57 @@ + LibMMS + + library for parsing + Microsoft Media Streaming + protocol + + +What is LibMMS? +=============== + +LibMMS is common library for parsing mms:// and mmsh:// type network +streams. These are commonly used to stream Windows Media Video content +over the web. LibMMS itself is only for receiving MMS stream, it +doesn't handle sending at all. If you need streaming functionality, +look for other protocol, such as RT(S)P. MMS is legacy thing, being +closed with no specs and abandoned even by its creator, the Microsoft Corp. + +Why LibMMS? +=========== + +LibMMS was created out of need for common library that would remedy +current situation where each Free Software project maintains its own +implementation of MMS protocol handling, creating unnecessary code and +effort duplication and introducing feature disparity as not every +bugfix gets into each of them. It also addresses need for LGPL +licensed code for that task, as every current implementation is +licensed as GPL, thus knocking out many projects that do not use GPL +themselves. + +Howto LibMMS? +============= + +LibMMS is intended to be small and simple, being useful for any +project that needs MMS handling functionality. It is constructed in +such a way that allows plugging custom I/O implementation, thus being +easy to integrate with custom framework, and providing a way to add +timeouted or cancelable I/O etc. + +Hmm, you said no specs? How so? +=============================== + +LibMMS code is based on amazing work done by SDP guys (http://get.to/sdp) +Without the specs they've reverse-engineered, there won't be any free +implementation of MMS handling today. + +How is LibMMS licensed? +======================= + +LibMMS is Free Software, licensed under GNU Library General Public +License. Original code comes from Xine project (http://xine.sf.net), +and it got separated for number of reasons, one of them being desire +to provide non GPL projects with library they could use. There's a +number of valuable LGPL projects locked out by GPL libraries, besides +we strongly feel that GPL is bad license for a library, as it's not +library thing to mess with its users licensing by vague interpretations +of wether linking makes binary derived work. We want libraries to be +used, and for that, we need to stay clean.
\ No newline at end of file diff --git a/plugins/mms/libmms/README.LICENSE b/plugins/mms/libmms/README.LICENSE new file mode 100644 index 00000000..15dfbd25 --- /dev/null +++ b/plugins/mms/libmms/README.LICENSE @@ -0,0 +1,6 @@ +Original GPL code was taken from Xine project +(http://xine.sf.net). Relicensed to LGPL with explicit approval from +all copyright holders, if you're interested, you can see thread at: + +<FIXME: add link to xine's mailing archives, import mails from my +local mailbox>
\ No newline at end of file diff --git a/plugins/mms/libmms/asfheader.h b/plugins/mms/libmms/asfheader.h new file mode 100644 index 00000000..2aaffb39 --- /dev/null +++ b/plugins/mms/libmms/asfheader.h @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2000-2003 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can 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. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 + * + * $Id: asfheader.h,v 1.2 2005/12/12 09:24:19 theuraeus Exp $ + * + * demultiplexer for asf streams + * + * based on ffmpeg's + * ASF compatible encoder and decoder. + * Copyright (c) 2000, 2001 Gerard Lantau. + * + * GUID list from avifile + * some other ideas from MPlayer + */ + +#ifndef ASFHEADER_H +#define ASFHEADER_H + +/* + * define asf GUIDs (list from avifile) + */ +#define GUID_ERROR 0 + + /* base ASF objects */ +#define GUID_ASF_HEADER 1 +#define GUID_ASF_DATA 2 +#define GUID_ASF_SIMPLE_INDEX 3 +#define GUID_INDEX 4 +#define GUID_MEDIA_OBJECT_INDEX 5 +#define GUID_TIMECODE_INDEX 6 + + /* header ASF objects */ +#define GUID_ASF_FILE_PROPERTIES 7 +#define GUID_ASF_STREAM_PROPERTIES 8 +#define GUID_ASF_HEADER_EXTENSION 9 +#define GUID_ASF_CODEC_LIST 10 +#define GUID_ASF_SCRIPT_COMMAND 11 +#define GUID_ASF_MARKER 12 +#define GUID_ASF_BITRATE_MUTUAL_EXCLUSION 13 +#define GUID_ASF_ERROR_CORRECTION 14 +#define GUID_ASF_CONTENT_DESCRIPTION 15 +#define GUID_ASF_EXTENDED_CONTENT_DESCRIPTION 16 +#define GUID_ASF_STREAM_BITRATE_PROPERTIES 17 +#define GUID_ASF_EXTENDED_CONTENT_ENCRYPTION 18 +#define GUID_ASF_PADDING 19 + + /* stream properties object stream type */ +#define GUID_ASF_AUDIO_MEDIA 20 +#define GUID_ASF_VIDEO_MEDIA 21 +#define GUID_ASF_COMMAND_MEDIA 22 +#define GUID_ASF_JFIF_MEDIA 23 +#define GUID_ASF_DEGRADABLE_JPEG_MEDIA 24 +#define GUID_ASF_FILE_TRANSFER_MEDIA 25 +#define GUID_ASF_BINARY_MEDIA 26 + + /* stream properties object error correction type */ +#define GUID_ASF_NO_ERROR_CORRECTION 27 +#define GUID_ASF_AUDIO_SPREAD 28 + + /* mutual exclusion object exlusion type */ +#define GUID_ASF_MUTEX_BITRATE 29 +#define GUID_ASF_MUTEX_UKNOWN 30 + + /* header extension */ +#define GUID_ASF_RESERVED_1 31 + + /* script command */ +#define GUID_ASF_RESERVED_SCRIPT_COMMNAND 32 + + /* marker object */ +#define GUID_ASF_RESERVED_MARKER 33 + + /* various */ +/* +#define GUID_ASF_HEAD2 27 +*/ +#define GUID_ASF_AUDIO_CONCEAL_NONE 34 +#define GUID_ASF_CODEC_COMMENT1_HEADER 35 +#define GUID_ASF_2_0_HEADER 36 +#define GUID_ASF_EXTENDED_STREAM_PROPERTIES 37 + +#define GUID_END 38 + + +/* asf stream types */ +#define ASF_STREAM_TYPE_UNKNOWN 0 +#define ASF_STREAM_TYPE_AUDIO 1 +#define ASF_STREAM_TYPE_VIDEO 2 +#define ASF_STREAM_TYPE_CONTROL 3 +#define ASF_STREAM_TYPE_JFIF 4 +#define ASF_STREAM_TYPE_DEGRADABLE_JPEG 5 +#define ASF_STREAM_TYPE_FILE_TRANSFER 6 +#define ASF_STREAM_TYPE_BINARY 7 + +#define ASF_MAX_NUM_STREAMS 23 + +#ifndef GUID_DEFINED +#define GUID_DEFINED + +typedef struct _GUID { /* size is 16 */ + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} GUID; + +#endif /* !GUID_DEFINED */ + +static const struct +{ + const char* name; + const GUID guid; +} guids[] = +{ + { "error", + { 0x0,} }, + + + /* base ASF objects */ + { "header", + { 0x75b22630, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} }, + + { "data", + { 0x75b22636, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} }, + + { "simple index", + { 0x33000890, 0xe5b1, 0x11cf, { 0x89, 0xf4, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xcb }} }, + + { "index", + { 0xd6e229d3, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + { "media object index", + { 0xfeb103f8, 0x12ad, 0x4c64, { 0x84, 0x0f, 0x2a, 0x1d, 0x2f, 0x7a, 0xd4, 0x8c }} }, + + { "timecode index", + { 0x3cb73fd0, 0x0c4a, 0x4803, { 0x95, 0x3d, 0xed, 0xf7, 0xb6, 0x22, 0x8f, 0x0c }} }, + + /* header ASF objects */ + { "file properties", + { 0x8cabdca1, 0xa947, 0x11cf, { 0x8e, 0xe4, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + { "stream header", + { 0xb7dc0791, 0xa9b7, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + { "header extension", + { 0x5fbf03b5, 0xa92e, 0x11cf, { 0x8e, 0xe3, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + { "codec list", + { 0x86d15240, 0x311d, 0x11d0, { 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "script command", + { 0x1efb1a30, 0x0b62, 0x11d0, { 0xa3, 0x9b, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "marker", + { 0xf487cd01, 0xa951, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + { "bitrate mutual exclusion", + { 0xd6e229dc, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + { "error correction", + { 0x75b22635, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} }, + + { "content description", + { 0x75b22633, 0x668e, 0x11cf, { 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c }} }, + + { "extended content description", + { 0xd2d0a440, 0xe307, 0x11d2, { 0x97, 0xf0, 0x00, 0xa0, 0xc9, 0x5e, 0xa8, 0x50 }} }, + + { "stream bitrate properties", /* (http://get.to/sdp) */ + { 0x7bf875ce, 0x468d, 0x11d1, { 0x8d, 0x82, 0x00, 0x60, 0x97, 0xc9, 0xa2, 0xb2 }} }, + + { "extended content encryption", + { 0x298ae614, 0x2622, 0x4c17, { 0xb9, 0x35, 0xda, 0xe0, 0x7e, 0xe9, 0x28, 0x9c }} }, + + { "padding", + { 0x1806d474, 0xcadf, 0x4509, { 0xa4, 0xba, 0x9a, 0xab, 0xcb, 0x96, 0xaa, 0xe8 }} }, + + + /* stream properties object stream type */ + { "audio media", + { 0xf8699e40, 0x5b4d, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "video media", + { 0xbc19efc0, 0x5b4d, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "command media", + { 0x59dacfc0, 0x59e6, 0x11d0, { 0xa3, 0xac, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "JFIF media (JPEG)", + { 0xb61be100, 0x5b4e, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "Degradable JPEG media", + { 0x35907de0, 0xe415, 0x11cf, { 0xa9, 0x17, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "File Transfer media", + { 0x91bd222c, 0xf21c, 0x497a, { 0x8b, 0x6d, 0x5a, 0xa8, 0x6b, 0xfc, 0x01, 0x85 }} }, + + { "Binary media", + { 0x3afb65e2, 0x47ef, 0x40f2, { 0xac, 0x2c, 0x70, 0xa9, 0x0d, 0x71, 0xd3, 0x43 }} }, + + /* stream properties object error correction */ + { "no error correction", + { 0x20fb5700, 0x5b55, 0x11cf, { 0xa8, 0xfd, 0x00, 0x80, 0x5f, 0x5c, 0x44, 0x2b }} }, + + { "audio spread", + { 0xbfc3cd50, 0x618f, 0x11cf, { 0x8b, 0xb2, 0x00, 0xaa, 0x00, 0xb4, 0xe2, 0x20 }} }, + + + /* mutual exclusion object exlusion type */ + { "mutex bitrate", + { 0xd6e22a01, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + { "mutex unknown", + { 0xd6e22a02, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + + /* header extension */ + { "reserved_1", + { 0xabd3d211, 0xa9ba, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + + + /* script command */ + { "reserved script command", + { 0x4B1ACBE3, 0x100B, 0x11D0, { 0xA3, 0x9B, 0x00, 0xA0, 0xC9, 0x03, 0x48, 0xF6 }} }, + + /* marker object */ + { "reserved marker", + { 0x4CFEDB20, 0x75F6, 0x11CF, { 0x9C, 0x0F, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xCB }} }, + + /* various */ + /* Already defined (reserved_1) + { "head2", + { 0xabd3d211, 0xa9ba, 0x11cf, { 0x8e, 0xe6, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65 }} }, + */ + { "audio conceal none", + { 0x49f1a440, 0x4ece, 0x11d0, { 0xa3, 0xac, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "codec comment1 header", + { 0x86d15241, 0x311d, 0x11d0, { 0xa3, 0xa4, 0x00, 0xa0, 0xc9, 0x03, 0x48, 0xf6 }} }, + + { "asf 2.0 header", + { 0xd6e229d1, 0x35da, 0x11d1, { 0x90, 0x34, 0x00, 0xa0, 0xc9, 0x03, 0x49, 0xbe }} }, + + { "extended stream properties", + { 0x14e6a5cb, 0xc672, 0x4332, { 0x83, 0x99, 0xa9, 0x69, 0x52, 0x06, 0x5b, 0x5a }} }, + +}; + +#endif diff --git a/plugins/mms/libmms/bswap.h b/plugins/mms/libmms/bswap.h new file mode 100644 index 00000000..a24460ab --- /dev/null +++ b/plugins/mms/libmms/bswap.h @@ -0,0 +1,280 @@ +#ifndef BSWAP_H_INCLUDED +#define BSWAP_H_INCLUDED + +/* + * Copyright (C) 2004 Maciej Katafiasz <mathrick@users.sourceforge.net> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library 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 + */ + + +/* NOTE: + * Now, to clear up confusion: LE_XX means "from LE to native, XX bits wide" + * I know it's not very clear naming (tell me about it, I + * misinterpreted in first version and caused bad nasty bug, *sigh*), + * but that's inherited code, will clean up as things go + * Oh, and one more thing -- they take *pointers*, not actual ints + */ + +/* Basic bit swapping functions + */ +#define GUINT16_SWAP_LE_BE_CONSTANT(val) ((guint16) ( \ + (guint16) ((guint16) (val) >> 8) | \ + (guint16) ((guint16) (val) << 8))) + +#define GUINT32_SWAP_LE_BE_CONSTANT(val) ((guint32) ( \ + (((guint32) (val) & (guint32) 0x000000ffU) << 24) | \ + (((guint32) (val) & (guint32) 0x0000ff00U) << 8) | \ + (((guint32) (val) & (guint32) 0x00ff0000U) >> 8) | \ + (((guint32) (val) & (guint32) 0xff000000U) >> 24))) + +#define GUINT64_SWAP_LE_BE_CONSTANT(val) ((guint64) ( \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x00000000000000ffU)) << 56) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x000000000000ff00U)) << 40) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x0000000000ff0000U)) << 24) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x00000000ff000000U)) << 8) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x000000ff00000000U)) >> 8) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x0000ff0000000000U)) >> 24) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0x00ff000000000000U)) >> 40) | \ + (((guint64) (val) & \ + (guint64) G_GINT64_CONSTANT (0xff00000000000000U)) >> 56))) + +/* Arch specific stuff for speed + */ +#if defined (__GNUC__) && (__GNUC__ >= 2) && defined (__OPTIMIZE__) +# if defined (__i386__) +# define GUINT16_SWAP_LE_BE_IA32(val) \ + (__extension__ \ + ({ register guint16 __v, __x = ((guint16) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT16_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("rorw $8, %w0" \ + : "=r" (__v) \ + : "0" (__x) \ + : "cc"); \ + __v; })) +# if !defined (__i486__) && !defined (__i586__) \ + && !defined (__pentium__) && !defined (__i686__) \ + && !defined (__pentiumpro__) && !defined (__pentium4__) +# define GUINT32_SWAP_LE_BE_IA32(val) \ + (__extension__ \ + ({ register guint32 __v, __x = ((guint32) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("rorw $8, %w0\n\t" \ + "rorl $16, %0\n\t" \ + "rorw $8, %w0" \ + : "=r" (__v) \ + : "0" (__x) \ + : "cc"); \ + __v; })) +# else /* 486 and higher has bswap */ +# define GUINT32_SWAP_LE_BE_IA32(val) \ + (__extension__ \ + ({ register guint32 __v, __x = ((guint32) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("bswap %0" \ + : "=r" (__v) \ + : "0" (__x)); \ + __v; })) +# endif /* processor specific 32-bit stuff */ +# define GUINT64_SWAP_LE_BE_IA32(val) \ + (__extension__ \ + ({ union { guint64 __ll; \ + guint32 __l[2]; } __w, __r; \ + __w.__ll = ((guint64) (val)); \ + if (__builtin_constant_p (__w.__ll)) \ + __r.__ll = GUINT64_SWAP_LE_BE_CONSTANT (__w.__ll); \ + else \ + { \ + __r.__l[0] = GUINT32_SWAP_LE_BE (__w.__l[1]); \ + __r.__l[1] = GUINT32_SWAP_LE_BE (__w.__l[0]); \ + } \ + __r.__ll; })) + /* Possibly just use the constant version and let gcc figure it out? */ +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_IA32 (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_IA32 (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_IA32 (val)) +# elif defined (__ia64__) +# define GUINT16_SWAP_LE_BE_IA64(val) \ + (__extension__ \ + ({ register guint16 __v, __x = ((guint16) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT16_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ __volatile__ ("shl %0 = %1, 48 ;;" \ + "mux1 %0 = %0, @rev ;;" \ + : "=r" (__v) \ + : "r" (__x)); \ + __v; })) +# define GUINT32_SWAP_LE_BE_IA64(val) \ + (__extension__ \ + ({ register guint32 __v, __x = ((guint32) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ __volatile__ ("shl %0 = %1, 32 ;;" \ + "mux1 %0 = %0, @rev ;;" \ + : "=r" (__v) \ + : "r" (__x)); \ + __v; })) +# define GUINT64_SWAP_LE_BE_IA64(val) \ + (__extension__ \ + ({ register guint64 __v, __x = ((guint64) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT64_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ __volatile__ ("mux1 %0 = %1, @rev ;;" \ + : "=r" (__v) \ + : "r" (__x)); \ + __v; })) +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_IA64 (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_IA64 (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_IA64 (val)) +# elif defined (__x86_64__) +# define GUINT32_SWAP_LE_BE_X86_64(val) \ + (__extension__ \ + ({ register guint32 __v, __x = ((guint32) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT32_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("bswapl %0" \ + : "=r" (__v) \ + : "0" (__x)); \ + __v; })) +# define GUINT64_SWAP_LE_BE_X86_64(val) \ + (__extension__ \ + ({ register guint64 __v, __x = ((guint64) (val)); \ + if (__builtin_constant_p (__x)) \ + __v = GUINT64_SWAP_LE_BE_CONSTANT (__x); \ + else \ + __asm__ ("bswapq %0" \ + : "=r" (__v) \ + : "0" (__x)); \ + __v; })) + /* gcc seems to figure out optimal code for this on its own */ +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_CONSTANT (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_X86_64 (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_X86_64 (val)) +# else /* generic gcc */ +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_CONSTANT (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_CONSTANT (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_CONSTANT (val)) +# endif +#else /* generic */ +# define GUINT16_SWAP_LE_BE(val) (GUINT16_SWAP_LE_BE_CONSTANT (val)) +# define GUINT32_SWAP_LE_BE(val) (GUINT32_SWAP_LE_BE_CONSTANT (val)) +# define GUINT64_SWAP_LE_BE(val) (GUINT64_SWAP_LE_BE_CONSTANT (val)) +#endif /* generic */ + +#define GUINT16_SWAP_LE_PDP(val) ((guint16) (val)) +#define GUINT16_SWAP_BE_PDP(val) (GUINT16_SWAP_LE_BE (val)) +#define GUINT32_SWAP_LE_PDP(val) ((guint32) ( \ + (((guint32) (val) & (guint32) 0x0000ffffU) << 16) | \ + (((guint32) (val) & (guint32) 0xffff0000U) >> 16))) +#define GUINT32_SWAP_BE_PDP(val) ((guint32) ( \ + (((guint32) (val) & (guint32) 0x00ff00ffU) << 8) | \ + (((guint32) (val) & (guint32) 0xff00ff00U) >> 8))) + + +/* The G*_TO_?E() macros are defined in glibconfig.h. + * The transformation is symmetric, so the FROM just maps to the TO. + */ +#define GINT16_FROM_LE(val) (GINT16_TO_LE (val)) +#define GUINT16_FROM_LE(val) (GUINT16_TO_LE (val)) +#define GINT16_FROM_BE(val) (GINT16_TO_BE (val)) +#define GUINT16_FROM_BE(val) (GUINT16_TO_BE (val)) +#define GINT32_FROM_LE(val) (GINT32_TO_LE (val)) +#define GUINT32_FROM_LE(val) (GUINT32_TO_LE (val)) +#define GINT32_FROM_BE(val) (GINT32_TO_BE (val)) +#define GUINT32_FROM_BE(val) (GUINT32_TO_BE (val)) + +#define GINT64_FROM_LE(val) (GINT64_TO_LE (val)) +#define GUINT64_FROM_LE(val) (GUINT64_TO_LE (val)) +#define GINT64_FROM_BE(val) (GINT64_TO_BE (val)) +#define GUINT64_FROM_BE(val) (GUINT64_TO_BE (val)) + +#define GLONG_FROM_LE(val) (GLONG_TO_LE (val)) +#define GULONG_FROM_LE(val) (GULONG_TO_LE (val)) +#define GLONG_FROM_BE(val) (GLONG_TO_BE (val)) +#define GULONG_FROM_BE(val) (GULONG_TO_BE (val)) + +#define GINT_FROM_LE(val) (GINT_TO_LE (val)) +#define GUINT_FROM_LE(val) (GUINT_TO_LE (val)) +#define GINT_FROM_BE(val) (GINT_TO_BE (val)) +#define GUINT_FROM_BE(val) (GUINT_TO_BE (val)) + +#define GSIZE_FROM_LE(val) (GSIZE_TO_LE (val)) +#define GSSIZE_FROM_LE(val) (GSSIZE_TO_LE (val)) +#define GSIZE_FROM_BE(val) (GSIZE_TO_BE (val)) +#define GSSIZE_FROM_BE(val) (GSSIZE_TO_BE (val)) + + +/* Portable versions of host-network order stuff + */ +#define g_ntohl(val) (GUINT32_FROM_BE (val)) +#define g_ntohs(val) (GUINT16_FROM_BE (val)) +#define g_htonl(val) (GUINT32_TO_BE (val)) +#define g_htons(val) (GUINT16_TO_BE (val)) + +#if WORDS_BIGENDIAN +#define GUINT64_TO_BE(val) (val) +#define GUINT32_TO_BE(val) (val) +#define GUINT16_TO_BE(val) (val) +#define GUINT64_TO_LE(val) GUINT64_SWAP_LE_BE(val) +#define GUINT32_TO_LE(val) GUINT32_SWAP_LE_BE(val) +#define GUINT16_TO_LE(val) GUINT16_SWAP_LE_BE(val) +#define GINT64_TO_BE(val) (val) +#define GINT32_TO_BE(val) (val) +#define GINT16_TO_BE(val) (val) +#define GINT64_TO_LE(val) GUINT64_SWAP_LE_BE(val) +#define GINT32_TO_LE(val) GUINT32_SWAP_LE_BE(val) +#define GINT16_TO_LE(val) GUINT16_SWAP_LE_BE(val) +#else +#define GUINT64_TO_BE(val) GUINT64_SWAP_LE_BE(val) +#define GUINT32_TO_BE(val) GUINT32_SWAP_LE_BE(val) +#define GUINT16_TO_BE(val) GUINT16_SWAP_LE_BE(val) +#define GUINT64_TO_LE(val) (val) +#define GUINT32_TO_LE(val) (val) +#define GUINT16_TO_LE(val) (val) +#define GINT64_TO_BE(val) GUINT64_SWAP_LE_BE(val) +#define GINT32_TO_BE(val) GUINT32_SWAP_LE_BE(val) +#define GINT16_TO_BE(val) GUINT16_SWAP_LE_BE(val) +#define GINT64_TO_LE(val) (val) +#define GINT32_TO_LE(val) (val) +#define GINT16_TO_LE(val) (val) +#endif + +#define LE_16(val) (GINT16_FROM_LE (*((u_int16_t*)(val)))) +#define BE_16(val) (GINT16_FROM_BE (*((u_int16_t*)(val)))) +#define LE_32(val) (GINT32_FROM_LE (*((u_int32_t*)(val)))) +#define BE_32(val) (GINT32_FROM_BE (*((u_int32_t*)(val)))) + +#define LE_64(val) (GINT64_FROM_LE (*((u_int64_t*)(val)))) +#define BE_64(val) (GINT64_FROM_BE (*((u_int64_t*)(val)))) + +#endif /* BSWAP_H_INCLUDED */ diff --git a/plugins/mms/libmms/mms-common.h b/plugins/mms/libmms/mms-common.h new file mode 100644 index 00000000..444d35a0 --- /dev/null +++ b/plugins/mms/libmms/mms-common.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2010 Hans de Goede <j.w.r.degoede@hhs.nl> + * + * This file is part of libmms a free mms protocol library + * + * libmms is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libmss is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + */ + +/* This file contains code which is shared between the mms and mmsh protocol + handling code. */ + +#ifndef __MMS_COMMON_H +#define __MMS_COMMON_H + +typedef struct mms_stream_s mms_stream_t; +struct mms_stream_s { + int stream_id; + int stream_type; + uint32_t bitrate; + uint32_t bitrate_pos; +}; + +#endif diff --git a/plugins/mms/libmms/mms.c b/plugins/mms/libmms/mms.c new file mode 100644 index 00000000..7e0d4a07 --- /dev/null +++ b/plugins/mms/libmms/mms.c @@ -0,0 +1,1802 @@ +/* + * Copyright (C) 2002-2004 the xine project + * + * This file is part of LibMMS, an MMS protocol handling library. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the ree Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 + * + * $Id: mms.c,v 1.31 2007/12/11 20:35:01 jwrdegoede Exp $ + * + * MMS over TCP protocol + * based on work from major mms + * utility functions to handle communication with an mms server + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <unistd.h> +#include <stdio.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <time.h> +#include <iconv.h> + +/********** logging **********/ +#define lprintf(...) if (getenv("LIBMMS_DEBUG")) fprintf(stderr, __VA_ARGS__) + +#define __MMS_C__ + +#include "bswap.h" +#include "mms.h" +#include "asfheader.h" +#include "uri.h" +#include "mms-common.h" + +/* + * mms specific types + */ + +#define MMST_PORT 1755 + +#define BUF_SIZE 102400 + +#define CMD_HEADER_LEN 40 +#define CMD_PREFIX_LEN 8 +#define CMD_BODY_LEN 1024 * 16 /* FIXME: make this dynamic */ + +#define ASF_HEADER_LEN (8192 * 2) + + +#define MMS_PACKET_ERR 0 +#define MMS_PACKET_COMMAND 1 +#define MMS_PACKET_ASF_HEADER 2 +#define MMS_PACKET_ASF_PACKET 3 + +#define ASF_HEADER_PACKET_ID_TYPE 2 +#define ASF_MEDIA_PACKET_ID_TYPE 4 + + +typedef struct mms_buffer_s mms_buffer_t; +struct mms_buffer_s { + uint8_t *buffer; + int pos; +}; + +typedef struct mms_packet_header_s mms_packet_header_t; +struct mms_packet_header_s { + uint32_t packet_len; + uint8_t flags; + uint8_t packet_id_type; + uint32_t packet_seq; +}; + +struct mms_s { + + int s; + + /* url parsing */ + GURI *guri; + char *url; + char *proto; + char *host; + int port; + char *user; + char *password; + char *uri; + + /* command to send */ + char scmd[CMD_HEADER_LEN + CMD_BODY_LEN]; + char *scmd_body; /* pointer to &scmd[CMD_HEADER_LEN] */ + int scmd_len; /* num bytes written in header */ + + char str[1024]; /* scratch buffer to built strings */ + + /* receive buffer */ + uint8_t buf[BUF_SIZE]; + int buf_size; + int buf_read; + off_t buf_packet_seq_offset; /* packet sequence offset residing in + buf */ + + uint8_t asf_header[ASF_HEADER_LEN]; + uint32_t asf_header_len; + uint32_t asf_header_read; + int seq_num; + int num_stream_ids; + mms_stream_t streams[ASF_MAX_NUM_STREAMS]; + uint8_t packet_id_type; + off_t start_packet_seq; /* for live streams != 0, need to keep it around */ + int need_discont; /* whether we need to set start_packet_seq */ + uint32_t asf_packet_len; + uint64_t file_len; + uint64_t time_len; /* playback time in 100 nanosecs (10^-7) */ + uint64_t preroll; + uint64_t asf_num_packets; + char guid[37]; + int bandwidth; + + int has_audio; + int has_video; + int live_flag; + int seekable; + off_t current_pos; + int eos; +}; + +static int fallback_io_select(void *data, int socket, int state, int timeout_msec) +{ + fd_set set; + struct timeval tv = { timeout_msec / 1000, (timeout_msec % 1000) * 1000}; + FD_ZERO(&set); + FD_SET(socket, &set); + return select(1, (state == MMS_IO_READ_READY) ? &set : NULL, + (state == MMS_IO_WRITE_READY) ? &set : NULL, NULL, &tv); +} + +static off_t fallback_io_read(void *data, int socket, char *buf, off_t num) +{ + off_t len = 0, ret; +/* lprintf("%d\n", fallback_io_select(data, socket, MMS_IO_READ_READY, 1000)); */ + errno = 0; + while (len < num) + { + ret = (off_t)read(socket, buf + len, num - len); + if(ret == 0) + break; /* EOF */ + if(ret < 0) { + lprintf("mms: read error @ len = %lld: %s\n", (long long int) len, + strerror(errno)); + switch(errno) + { + case EAGAIN: + continue; + default: + /* if already read something, return it, we will fail next time */ + return len ? len : ret; + } + } + len += ret; + } + return len; +} + +static off_t fallback_io_write(void *data, int socket, char *buf, off_t num) +{ + return (off_t)write(socket, buf, num); +} + +static int fallback_io_tcp_connect(void *data, const char *host, int port) +{ + + struct hostent *h; + int i, s; + + h = gethostbyname(host); + if (h == NULL) { + lprintf("mms: unable to resolve host: %s\n", host); + return -1; + } + + s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == -1) { + lprintf("mms: failed to create socket: %s\n", strerror(errno)); + return -1; + } + + if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) & ~O_NONBLOCK) == -1) { + lprintf("mms: failed to set socket flags: %s\n", strerror(errno)); + return -1; + } + + for (i = 0; h->h_addr_list[i]; i++) { + struct in_addr ia; + struct sockaddr_in sin; + + memcpy (&ia, h->h_addr_list[i], 4); + sin.sin_family = AF_INET; + sin.sin_addr = ia; + sin.sin_port = htons(port); + + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) ==-1 && errno != EINPROGRESS) { + continue; + } + + return s; + } + close(s); + return -1; +} + + +static mms_io_t fallback_io = + { + &fallback_io_select, + NULL, + &fallback_io_read, + NULL, + &fallback_io_write, + NULL, + &fallback_io_tcp_connect, + NULL, + }; + +static mms_io_t default_io = { + &fallback_io_select, + NULL, + &fallback_io_read, + NULL, + &fallback_io_write, + NULL, + &fallback_io_tcp_connect, + NULL, + }; + + +#define io_read(io, args...) ((io) ? (io)->read(io->read_data , ## args) : default_io.read(NULL , ## args)) +#define io_write(io, args...) ((io) ? (io)->write(io->write_data , ## args) : default_io.write(NULL , ## args)) +#define io_select(io, args...) ((io) ? (io)->select(io->select_data , ## args) : default_io.select(NULL , ## args)) +#define io_connect(io, args...) ((io) ? (io)->connect(io->connect_data , ## args) : default_io.connect(NULL , ## args)) + +const mms_io_t* mms_get_default_io_impl() +{ + return &default_io; +} + +void mms_set_default_io_impl(const mms_io_t *io) +{ + if(io->select) + { + default_io.select = io->select; + default_io.select_data = io->select_data; + } else + { + default_io.select = fallback_io.select; + default_io.select_data = fallback_io.select_data; + } + if(io->read) + { + default_io.read = io->read; + default_io.read_data = io->read_data; + } else + { + default_io.read = fallback_io.read; + default_io.read_data = fallback_io.read_data; + } + if(io->write) + { + default_io.write = io->write; + default_io.write_data = io->write_data; + } else + { + default_io.write = fallback_io.write; + default_io.write_data = fallback_io.write_data; + } + if(io->connect) + { + default_io.connect = io->connect; + default_io.connect_data = io->connect_data; + } else + { + default_io.connect = fallback_io.connect; + default_io.connect_data = fallback_io.connect_data; + } +} + +static void mms_buffer_init (mms_buffer_t *mms_buffer, uint8_t *buffer) { + mms_buffer->buffer = buffer; + mms_buffer->pos = 0; +} + +static void mms_buffer_put_8 (mms_buffer_t *mms_buffer, uint8_t value) { + + mms_buffer->buffer[mms_buffer->pos] = value & 0xff; + + mms_buffer->pos += 1; +} + +#if 0 +static void mms_buffer_put_16 (mms_buffer_t *mms_buffer, uint16_t value) { + + mms_buffer->buffer[mms_buffer->pos] = value & 0xff; + mms_buffer->buffer[mms_buffer->pos + 1] = (value >> 8) & 0xff; + + mms_buffer->pos += 2; +} +#endif + +static void mms_buffer_put_32 (mms_buffer_t *mms_buffer, uint32_t value) { + + mms_buffer->buffer[mms_buffer->pos] = value & 0xff; + mms_buffer->buffer[mms_buffer->pos + 1] = (value >> 8) & 0xff; + mms_buffer->buffer[mms_buffer->pos + 2] = (value >> 16) & 0xff; + mms_buffer->buffer[mms_buffer->pos + 3] = (value >> 24) & 0xff; + + mms_buffer->pos += 4; +} + +static int get_guid (unsigned char *buffer, int offset) { + int i; + GUID g; + + g.Data1 = LE_32(buffer + offset); + g.Data2 = LE_16(buffer + offset + 4); + g.Data3 = LE_16(buffer + offset + 6); + for(i = 0; i < 8; i++) { + g.Data4[i] = buffer[offset + 8 + i]; + } + + for (i = 1; i < GUID_END; i++) { + if (!memcmp(&g, &guids[i].guid, sizeof(GUID))) { + lprintf("mms: GUID: %s\n", guids[i].name); + return i; + } + } + + lprintf("mms: unknown GUID: 0x%x, 0x%x, 0x%x, " + "{ 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx }\n", + g.Data1, g.Data2, g.Data3, + g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], + g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]); + + return GUID_ERROR; +} + + +static void print_command (char *data, int len) { + +#ifdef DEBUG + int i; + int dir = LE_32 (data + 36) >> 16; + int comm = LE_32 (data + 36) & 0xFFFF; + + lprintf ("----------------------------------------------\n"); + if (dir == 3) { + lprintf ("send command 0x%02x, %d bytes\n", comm, len); + } else { + lprintf ("receive command 0x%02x, %d bytes\n", comm, len); + } + lprintf (" start sequence %08x\n", LE_32 (data + 0)); + lprintf (" command id %08x\n", LE_32 (data + 4)); + lprintf (" length %8x \n", LE_32 (data + 8)); + lprintf (" protocol %08x\n", LE_32 (data + 12)); + lprintf (" len8 %8x \n", LE_32 (data + 16)); + lprintf (" sequence # %08x\n", LE_32 (data + 20)); + lprintf (" len8 (II) %8x \n", LE_32 (data + 32)); + lprintf (" dir | comm %08x\n", LE_32 (data + 36)); + if (len >= 4) + lprintf (" prefix1 %08x\n", LE_32 (data + 40)); + if (len >= 8) + lprintf (" prefix2 %08x\n", LE_32 (data + 44)); + + for (i = (CMD_HEADER_LEN + CMD_PREFIX_LEN); i < (CMD_HEADER_LEN + CMD_PREFIX_LEN + len); i += 1) { + unsigned char c = data[i]; + + if ((c >= 32) && (c < 128)) + lprintf ("%c", c); + else + lprintf (" %02x ", c); + + } + if (len > CMD_HEADER_LEN) + lprintf ("\n"); + lprintf ("----------------------------------------------\n"); +#endif +} + + + +static int send_command (mms_io_t *io, mms_t *this, int command, + uint32_t prefix1, uint32_t prefix2, + int length) { + int len8; + off_t n; + mms_buffer_t command_buffer; + + len8 = (length + 7) / 8; + + this->scmd_len = 0; + + mms_buffer_init(&command_buffer, this->scmd); + mms_buffer_put_32 (&command_buffer, 0x00000001); /* start sequence */ + mms_buffer_put_32 (&command_buffer, 0xB00BFACE); /* #-)) */ + mms_buffer_put_32 (&command_buffer, len8 * 8 + 32); + mms_buffer_put_32 (&command_buffer, 0x20534d4d); /* protocol type "MMS " */ + mms_buffer_put_32 (&command_buffer, len8 + 4); + mms_buffer_put_32 (&command_buffer, this->seq_num); + this->seq_num++; + mms_buffer_put_32 (&command_buffer, 0x0); /* timestamp */ + mms_buffer_put_32 (&command_buffer, 0x0); + mms_buffer_put_32 (&command_buffer, len8 + 2); + mms_buffer_put_32 (&command_buffer, 0x00030000 | command); /* dir | command */ + /* end of the 40 byte command header */ + + mms_buffer_put_32 (&command_buffer, prefix1); + mms_buffer_put_32 (&command_buffer, prefix2); + + if (length & 7) + memset(this->scmd + length + CMD_HEADER_LEN + CMD_PREFIX_LEN, 0, 8 - (length & 7)); + + n = io_write(io, this->s, this->scmd, len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN); + if (n != (len8 * 8 + CMD_HEADER_LEN + CMD_PREFIX_LEN)) { + return 0; + } + + print_command (this->scmd, length); + + return 1; +} + +static int string_utf16(iconv_t url_conv, char *dest, char *src, int dest_len) +{ + char *ip = src, *op = dest; + size_t ip_len = strlen(src); + size_t op_len = dest_len - 2; /* reserve 2 bytes for 0 termination */ + + if (iconv(url_conv, &ip, &ip_len, &op, &op_len) == (size_t)-1) { + lprintf("mms: Error converting uri to unicode: %s\n", strerror(errno)); + return 0; + } + + /* 0 terminate the string */ + *op++ = 0; + *op++ = 0; + + return op - dest; +} + +/* + * return packet type + */ +static int get_packet_header (mms_io_t *io, mms_t *this, mms_packet_header_t *header) { + size_t len; + int packet_type; + + header->packet_len = 0; + header->packet_seq = 0; + header->flags = 0; + header->packet_id_type = 0; + len = io_read(io, this->s, this->buf, 8); + this->buf_packet_seq_offset = -1; + if (len != 8) + goto error; + + if (LE_32(this->buf + 4) == 0xb00bface) { + /* command packet */ + header->flags = this->buf[3]; + len = io_read(io, this->s, this->buf + 8, 4); + if (len != 4) + goto error; + + header->packet_len = LE_32(this->buf + 8) + 4; + if (header->packet_len > BUF_SIZE - 12) { + lprintf("mms: get_packet_header error cmd packet length > bufsize\n"); + header->packet_len = 0; + return MMS_PACKET_ERR; + } + packet_type = MMS_PACKET_COMMAND; + } else { + header->packet_seq = LE_32(this->buf); + header->packet_id_type = this->buf[4]; + header->flags = this->buf[5]; + header->packet_len = (LE_16(this->buf + 6) - 8) & 0xffff; + if (header->packet_id_type == ASF_HEADER_PACKET_ID_TYPE) { + packet_type = MMS_PACKET_ASF_HEADER; + } else { + packet_type = MMS_PACKET_ASF_PACKET; + } + } + + return packet_type; + +error: + lprintf("mms: error reading packet header\n"); + return MMS_PACKET_ERR; +} + + +static int get_packet_command (mms_io_t *io, mms_t *this, uint32_t packet_len) { + + + int command = 0; + size_t len; + + len = io_read(io, this->s, this->buf + 12, packet_len) ; + //this->buf_packet_seq_offset = -1; // already set in get_packet_header + if (len != packet_len) { + lprintf("mms: error reading command packet\n"); + return 0; + } + + print_command (this->buf, len); + + /* check protocol type ("MMS ") */ + if (LE_32(this->buf + 12) != 0x20534D4D) { + lprintf("mms: unknown protocol type: %c%c%c%c (0x%08X)\n", + this->buf[12], this->buf[13], this->buf[14], this->buf[15], + LE_32(this->buf + 12)); + return 0; + } + + command = LE_32 (this->buf + 36) & 0xFFFF; + lprintf("mms: received command = %02x, len: %d\n", command, packet_len); + + return command; +} + +static int get_answer (mms_io_t *io, mms_t *this) { + int command = 0; + mms_packet_header_t header; + + switch (get_packet_header (io, this, &header)) { + case MMS_PACKET_ERR: + break; + case MMS_PACKET_COMMAND: + command = get_packet_command (io, this, header.packet_len); + if (command == 0) + return 0; + + if (command == 0x1b) { + if (!send_command (io, this, 0x1b, 0, 0, 0)) { + lprintf("mms: error sending ping reply\n"); + return 0; + } + /* FIXME: limit recursion */ + command = get_answer (io, this); + } + break; + case MMS_PACKET_ASF_HEADER: + lprintf("mms: unexpected asf header packet\n"); + break; + case MMS_PACKET_ASF_PACKET: + lprintf("mms: unexpected asf packet\n"); + break; + } + + return command; +} + + +static int get_asf_header (mms_io_t *io, mms_t *this) { + + off_t len; + int stop = 0; + + this->asf_header_read = 0; + this->asf_header_len = 0; + + while (!stop) { + mms_packet_header_t header; + int command; + + switch (get_packet_header (io, this, &header)) { + case MMS_PACKET_ERR: + return 0; + case MMS_PACKET_COMMAND: + command = get_packet_command (io, this, header.packet_len); + if (command == 0) + return 0; + + if (command == 0x1b) { + if (!send_command (io, this, 0x1b, 0, 0, 0)) { + lprintf("mms: error sending ping reply\n"); + return 0; + } + command = get_answer (io, this); + } else { + lprintf("mms: unexpected command packet\n"); + } + break; + case MMS_PACKET_ASF_HEADER: + case MMS_PACKET_ASF_PACKET: + if (header.packet_len + this->asf_header_len > ASF_HEADER_LEN) { + lprintf("mms: asf packet too large: %d\n", + header.packet_len + this->asf_header_len); + return 0; + } + len = io_read(io, this->s, + this->asf_header + this->asf_header_len, header.packet_len); + if (len != header.packet_len) { + lprintf("mms: error reading asf header\n"); + return 0; + } + this->asf_header_len += header.packet_len; + lprintf("mms: header flags: %d\n", header.flags); + if ((header.flags == 0X08) || (header.flags == 0X0C)) + stop = 1; + break; + } + } + return 1; +} + +static void interp_stream_properties(mms_t *this, int i) +{ + uint16_t flags; + uint16_t stream_id; + int type; + int encrypted; + int guid; + + guid = get_guid(this->asf_header, i); + switch (guid) { + case GUID_ASF_AUDIO_MEDIA: + type = ASF_STREAM_TYPE_AUDIO; + this->has_audio = 1; + break; + + case GUID_ASF_VIDEO_MEDIA: + case GUID_ASF_JFIF_MEDIA: + case GUID_ASF_DEGRADABLE_JPEG_MEDIA: + type = ASF_STREAM_TYPE_VIDEO; + this->has_video = 1; + break; + + case GUID_ASF_COMMAND_MEDIA: + type = ASF_STREAM_TYPE_CONTROL; + break; + + default: + type = ASF_STREAM_TYPE_UNKNOWN; + } + + flags = LE_16(this->asf_header + i + 48); + stream_id = flags & 0x7F; + encrypted = flags >> 15; + + lprintf("mms: stream object, stream id: %d, type: %d, encrypted: %d\n", + stream_id, type, encrypted); + + if (this->num_stream_ids < ASF_MAX_NUM_STREAMS) { + this->streams[this->num_stream_ids].stream_type = type; + this->streams[this->num_stream_ids].stream_id = stream_id; + this->num_stream_ids++; + } else { + lprintf("mms: too many streams, skipping\n"); + } +} + +static void interp_asf_header (mms_t *this) { + + int i; + + this->asf_packet_len = 0; + this->num_stream_ids = 0; + this->asf_num_packets = 0; + /* + * parse header + */ + + i = 30; + while ((i + 24) <= this->asf_header_len) { + + int guid; + uint64_t length; + + guid = get_guid(this->asf_header, i); + length = LE_64(this->asf_header + i + 16); + + if ((i + length) > this->asf_header_len) return; + + switch (guid) { + + case GUID_ASF_FILE_PROPERTIES: + + this->asf_packet_len = LE_32(this->asf_header + i + 92); + if (this->asf_packet_len > BUF_SIZE) { + lprintf("mms: asf packet len too large: %d\n", this->asf_packet_len); + this->asf_packet_len = 0; + break; + } + this->file_len = LE_64(this->asf_header + i + 40); + this->time_len = LE_64(this->asf_header + i + 64); + //this->time_len = LE_64(this->asf_header + i + 72); + this->preroll = LE_64(this->asf_header + i + 80); + lprintf("mms: file object, packet length = %d (%d)\n", + this->asf_packet_len, LE_32(this->asf_header + i + 96)); + break; + + case GUID_ASF_STREAM_PROPERTIES: + interp_stream_properties(this, i + 24); + break; + + case GUID_ASF_STREAM_BITRATE_PROPERTIES: + { + uint16_t streams = LE_16(this->asf_header + i + 24); + uint16_t stream_id; + int j; + + for(j = 0; j < streams; j++) { + int stream_index; + stream_id = LE_16(this->asf_header + i + 24 + 2 + j * 6); + for(stream_index = 0; stream_index < this->num_stream_ids; stream_index++) { + if (this->streams[stream_index].stream_id == stream_id) + break; + } + if (stream_index < this->num_stream_ids) { + this->streams[stream_index].bitrate = LE_32(this->asf_header + i + 24 + 4 + j * 6); + this->streams[stream_index].bitrate_pos = i + 24 + 4 + j * 6; + lprintf("mms: stream id %d, bitrate %d\n", stream_id, + this->streams[stream_index].bitrate); + } else + lprintf ("mms: unknown stream id %d in bitrate properties\n", + stream_id); + } + } + break; + + case GUID_ASF_HEADER_EXTENSION: + { + if ((24 + 18 + 4) > length) + break; + + int size = LE_32(this->asf_header + i + 24 + 18); + int j = 24 + 18 + 4; + int l; + lprintf("mms: Extension header data size: %d\n", size); + + while ((j + 24) <= length) { + guid = get_guid(this->asf_header, i + j); + l = LE_64(this->asf_header + i + j + 16); + + if ((j + l) > length) + break; + + if (guid == GUID_ASF_EXTENDED_STREAM_PROPERTIES && + (24 + 64) <= l) { + int stream_no = LE_16(this->asf_header + i + j + 24 + 48); + int name_count = LE_16(this->asf_header + i + j + 24 + 60); + int ext_count = LE_16(this->asf_header + i + j + 24 + 62); + int ext_j = 24 + 64; + int x; + + lprintf("mms: l: %d\n", l); + lprintf("mms: Stream No: %d\n", stream_no); + lprintf("mms: ext_count: %d\n", ext_count); + + // Loop through the number of stream names + for (x = 0; x < name_count && (ext_j + 4) <= l; x++) { + int lang_id_index; + int stream_name_len; + + lang_id_index = LE_16(this->asf_header + i + j + ext_j); + ext_j += 2; + + stream_name_len = LE_16(this->asf_header + i + j + ext_j); + ext_j += stream_name_len + 2; + + lprintf("mms: Language id index: %d\n", lang_id_index); + lprintf("mms: Stream name Len: %d\n", stream_name_len); + } + + // Loop through the number of extension system info + for (x = 0; x < ext_count && (ext_j + 22) <= l; x++) { + ext_j += 18; + int len = LE_16(this->asf_header + i + j + ext_j); + ext_j += 4 + len; + } + + lprintf("mms: ext_j: %d\n", ext_j); + // Finally, we arrive at the interesting point: The optional Stream Property Object + if ((ext_j + 24) <= l) { + guid = get_guid(this->asf_header, i + j + ext_j); + int len = LE_64(this->asf_header + i + j + ext_j + 16); + if (guid == GUID_ASF_STREAM_PROPERTIES && + (ext_j + len) <= l) { + interp_stream_properties(this, i + j + ext_j + 24); + } + } else { + lprintf("mms: Sorry, field not long enough\n"); + } + } + j += l; + } + } + break; + + case GUID_ASF_DATA: + this->asf_num_packets = LE_64(this->asf_header + i + 40 - 24); + break; + } + + lprintf("mms: length: %llu\n", (unsigned long long)length); + i += length; + } +} + +const static char *const mmst_proto_s[] = { "mms", "mmst", NULL }; + +static int mmst_valid_proto (char *proto) { + int i = 0; + + if (!proto) + return 0; + + while(mmst_proto_s[i]) { + if (!strcasecmp(proto, mmst_proto_s[i])) { + return 1; + } + i++; + } + return 0; +} + +/* + * returns 1 on error + */ +static int mms_tcp_connect(mms_io_t *io, mms_t *this) { + if (!this->port) this->port = MMST_PORT; + + /* + * try to connect + */ + lprintf("mms: trying to connect to %s on port %d\n", this->host, this->port); + this->s = io_connect(io, this->host, this->port); + if (this->s == -1) { + lprintf("mms: failed to connect to %s\n", this->host); + return 1; + } + + lprintf("mms: connected\n"); + return 0; +} + +static void mms_gen_guid(char guid[]) { + static char digit[16] = "0123456789ABCDEF"; + int i = 0; + + srand(time(NULL)); + for (i = 0; i < 36; i++) { + guid[i] = digit[(int) ((16.0*rand())/(RAND_MAX+1.0))]; + } + guid[8] = '-'; guid[13] = '-'; guid[18] = '-'; guid[23] = '-'; + guid[36] = '\0'; +} + +const char *status_to_string(int status) +{ + switch (status) { + case 0x80070003: + return "Path not found"; + case 0x80070005: + return "Access Denied"; + default: + return "Unknown"; + } +} + +/* + * return 0 on error + */ +int static mms_choose_best_streams(mms_io_t *io, mms_t *this) { + int i; + int video_stream = -1; + int audio_stream = -1; + int max_arate = 0; + int min_vrate = 0; + int min_bw_left = 0; + int bandwitdh_left; + int res; + + /* command 0x33 */ + /* choose the best quality for the audio stream */ + /* i've never seen more than one audio stream */ + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_AUDIO: + if (this->streams[i].bitrate > max_arate) { + audio_stream = this->streams[i].stream_id; + max_arate = this->streams[i].bitrate; + } + break; + default: + break; + } + } + + /* choose a video stream adapted to the user bandwidth */ + bandwitdh_left = this->bandwidth - max_arate; + if (bandwitdh_left < 0) { + bandwitdh_left = 0; + } + lprintf("mms: bandwidth %d, left %d\n", this->bandwidth, bandwitdh_left); + + min_bw_left = bandwitdh_left; + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_VIDEO: + if (((bandwitdh_left - this->streams[i].bitrate) < min_bw_left) && + (bandwitdh_left >= this->streams[i].bitrate)) { + video_stream = this->streams[i].stream_id; + min_bw_left = bandwitdh_left - this->streams[i].bitrate; + } + break; + default: + break; + } + } + + /* choose the lower bitrate of */ + if (video_stream == -1 && this->has_video) { + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_VIDEO: + if ((this->streams[i].bitrate < min_vrate) || + (!min_vrate)) { + video_stream = this->streams[i].stream_id; + min_vrate = this->streams[i].bitrate; + } + break; + default: + break; + } + } + } + + lprintf("mms: selected streams: audio %d, video %d\n", audio_stream, video_stream); + memset (this->scmd_body, 0, 40); + for (i = 1; i < this->num_stream_ids; i++) { + this->scmd_body [ (i - 1) * 6 + 2 ] = 0xFF; + this->scmd_body [ (i - 1) * 6 + 3 ] = 0xFF; + this->scmd_body [ (i - 1) * 6 + 4 ] = this->streams[i].stream_id ; + this->scmd_body [ (i - 1) * 6 + 5 ] = this->streams[i].stream_id >> 8; + if ((this->streams[i].stream_id == audio_stream) || + (this->streams[i].stream_id == video_stream)) { + this->scmd_body [ (i - 1) * 6 + 6 ] = 0x00; + this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00; + } else { + lprintf("mms: disabling stream %d\n", this->streams[i].stream_id); + this->scmd_body [ (i - 1) * 6 + 6 ] = 0x02; + this->scmd_body [ (i - 1) * 6 + 7 ] = 0x00; + + /* forces the asf demuxer to not choose this stream */ + if (this->streams[i].bitrate_pos) { + if (this->streams[i].bitrate_pos+3 < ASF_HEADER_LEN) { + this->asf_header[this->streams[i].bitrate_pos ] = 0; + this->asf_header[this->streams[i].bitrate_pos + 1] = 0; + this->asf_header[this->streams[i].bitrate_pos + 2] = 0; + this->asf_header[this->streams[i].bitrate_pos + 3] = 0; + } else { + lprintf("mms: attempt to write beyond asf header limit\n"); + } + } + } + } + + lprintf("mms: send command 0x33\n"); + if (!send_command (io, this, 0x33, this->num_stream_ids, + 0xFFFF | this->streams[0].stream_id << 16, + this->num_stream_ids * 6 + 2)) { + lprintf("mms: mms_choose_best_streams failed\n"); + return 0; + } + + if ((res = get_answer (io, this)) != 0x21) { + lprintf("mms: unexpected response: %02x (0x21)\n", res); + return 0; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x21 status: %08x (%s)\n", + res, status_to_string(res)); + return 0; + } + + return 1; +} + +/* + * TODO: error messages + * network timing request + */ +/* FIXME: got somewhat broken during xine_stream_t->(void*) conversion */ +mms_t *mms_connect (mms_io_t *io, void *data, const char *url, int bandwidth) { + iconv_t url_conv = (iconv_t)-1; + mms_t *this; + int res; + uint32_t openid; + mms_buffer_t command_buffer; + + if (!url) + return NULL; + + /* FIXME: needs proper error-signalling work */ + this = (mms_t*) malloc (sizeof (mms_t)); + + this->url = strdup (url); + this->s = -1; + this->seq_num = 0; + this->scmd_body = this->scmd + CMD_HEADER_LEN + CMD_PREFIX_LEN; + this->asf_header_len = 0; + this->asf_header_read = 0; + this->num_stream_ids = 0; + this->asf_packet_len = 0; + this->start_packet_seq= 0; + this->need_discont = 1; + this->buf_size = 0; + this->buf_read = 0; + this->buf_packet_seq_offset = -1; + this->has_audio = 0; + this->has_video = 0; + this->bandwidth = bandwidth; + this->current_pos = 0; + this->eos = 0; + + this->guri = gnet_uri_new(this->url); + if(!this->guri) { + lprintf("mms: invalid url\n"); + goto fail; + } + + /* MMS wants unescaped (so not percent coded) strings */ + gnet_uri_unescape(this->guri); + + this->proto = this->guri->scheme; + this->user = this->guri->user; + this->host = this->guri->hostname; + this->port = this->guri->port; + this->password = this->guri->passwd; + this->uri = gnet_mms_helper(this->guri, 0); + + if(!this->uri) + goto fail; + + if (!mmst_valid_proto(this->proto)) { + lprintf("mms: unsupported protocol: %s\n", this->proto); + goto fail; + } + + if (mms_tcp_connect(io, this)) { + goto fail; + } + + url_conv = iconv_open("UTF-16LE", "UTF-8"); + if (url_conv == (iconv_t)-1) { + lprintf("mms: could not get iconv handle to convert url to unicode\n"); + goto fail; + } + + /* + * let the negotiations begin... + */ + + /* command 0x1 */ + lprintf("mms: send command 0x01\n"); + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x0003001C); + mms_gen_guid(this->guid); + sprintf(this->str, "NSPlayer/7.0.0.1956; {%s}; Host: %s", this->guid, + this->host); + res = string_utf16(url_conv, this->scmd_body + command_buffer.pos, this->str, + CMD_BODY_LEN - command_buffer.pos); + if(!res) + goto fail; + + if (!send_command(io, this, 1, 0, 0x0004000b, command_buffer.pos + res)) { + lprintf("mms: failed to send command 0x01\n"); + goto fail; + } + + if ((res = get_answer (io, this)) != 0x01) { + lprintf("mms: unexpected response: %02x (0x01)\n", res); + goto fail; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x01 status: %08x (%s)\n", + res, status_to_string(res)); + goto fail; + } + + /* TODO: insert network timing request here */ + /* command 0x2 */ + lprintf("mms: send command 0x02\n"); + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); + mms_buffer_put_32 (&command_buffer, 0x00989680); + mms_buffer_put_32 (&command_buffer, 0x00000002); + res = string_utf16(url_conv, this->scmd_body + command_buffer.pos, + "\\\\192.168.0.129\\TCP\\1037", + CMD_BODY_LEN - command_buffer.pos); + if(!res) + goto fail; + + if (!send_command(io, this, 2, 0, 0xffffffff, command_buffer.pos + res)) { + lprintf("mms: failed to send command 0x02\n"); + goto fail; + } + + switch (res = get_answer (io, this)) { + case 0x02: + /* protocol accepted */ + break; + case 0x03: + lprintf("mms: protocol failed\n"); + goto fail; + default: + lprintf("mms: unexpected response: %02x (0x02 or 0x03)\n", res); + goto fail; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x02 status: %08x (%s)\n", + res, status_to_string(res)); + goto fail; + } + + /* command 0x5 */ + { + mms_buffer_t command_buffer; + + lprintf("mms: send command 0x05\n"); + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + + res = string_utf16(url_conv, this->scmd_body + command_buffer.pos, + this->uri, CMD_BODY_LEN - command_buffer.pos); + if(!res) + goto fail; + + if (!send_command(io, this, 5, 1, 0, command_buffer.pos + res)) { + lprintf("mms: failed to send command 0x05\n"); + goto fail; + } + } + + switch (res = get_answer (io, this)) { + case 0x06: + { + int xx, yy; + /* no authentication required */ + openid = LE_32(this->buf + 48); + + /* Warning: sdp is not right here */ + xx = this->buf[62]; + yy = this->buf[63]; + this->live_flag = ((xx == 0) && ((yy & 0xf) == 2)); + this->seekable = !this->live_flag; + lprintf("mms: openid=%d, live: live_flag=%d, xx=%d, yy=%d\n", openid, this->live_flag, xx, yy); + } + break; + case 0x1A: + /* authentication request, not yet supported */ + lprintf("mms: authentication request, not yet supported\n"); + goto fail; + break; + default: + lprintf("mms: unexpected response: %02x (0x06 or 0x1A)\n", res); + goto fail; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x06 status: %08x (%s)\n", + res, status_to_string(res)); + goto fail; + } + + /* command 0x15 */ + lprintf("mms: send command 0x15\n"); + { + mms_buffer_t command_buffer; + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00008000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0x40AC2000); /* ?? */ + mms_buffer_put_32 (&command_buffer, ASF_HEADER_PACKET_ID_TYPE); /* Header Packet ID type */ + mms_buffer_put_32 (&command_buffer, 0x00000000); /* ?? */ + if (!send_command (io, this, 0x15, openid, 0, command_buffer.pos)) { + lprintf("mms: failed to send command 0x15\n"); + goto fail; + } + } + + if ((res = get_answer (io, this)) != 0x11) { + lprintf("mms: unexpected response: %02x (0x11)\n", res); + goto fail; + } + + res = LE_32(this->buf + 40); + if (res != 0) { + lprintf("mms: error answer 0x11 status: %08x (%s)\n", + res, status_to_string(res)); + goto fail; + } + + this->num_stream_ids = 0; + + if (!get_asf_header (io, this)) + goto fail; + + interp_asf_header (this); + if (!this->asf_packet_len || !this->num_stream_ids) + goto fail; + + if (!mms_choose_best_streams(io, this)) { + lprintf("mms: mms_choose_best_streams failed\n"); + goto fail; + } + + /* command 0x07 */ + this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE; + { + mms_buffer_t command_buffer; + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */ + mms_buffer_put_32 (&command_buffer, 0x00000000); + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */ + mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */ + mms_buffer_put_8 (&command_buffer, 0xFF); + mms_buffer_put_8 (&command_buffer, 0xFF); + mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */ + mms_buffer_put_32 (&command_buffer, this->packet_id_type); /* asf media packet id type */ + if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) { + lprintf("mms: failed to send command 0x07\n"); + goto fail; + } + } + + iconv_close(url_conv); + lprintf("mms: connect: passed\n"); + + return this; + +fail: + if (this->s != -1) + close (this->s); + if (this->url) + free(this->url); + if (this->guri) + gnet_uri_delete(this->guri); + if (this->uri) + free(this->uri); + if (url_conv != (iconv_t)-1) + iconv_close(url_conv); + + free (this); + return NULL; +} + +static int get_media_packet (mms_io_t *io, mms_t *this) { + mms_packet_header_t header; + off_t len; + + switch (get_packet_header (io, this, &header)) { + case MMS_PACKET_ERR: + return 0; + + case MMS_PACKET_COMMAND: + { + int command; + command = get_packet_command (io, this, header.packet_len); + + switch (command) { + case 0: + return 0; + + case 0x1e: + { + uint32_t error_code; + + /* Warning: sdp is incomplete. Do not stop if error_code==1 */ + error_code = LE_32(this->buf + CMD_HEADER_LEN); + lprintf("mms: End of the current stream. Continue=%d\n", error_code); + + if (error_code == 0) { + this->eos = 1; + return 0; + } + + } + break; + + case 0x20: + { + lprintf("mms: new stream.\n"); + /* asf header */ + if (!get_asf_header (io, this)) { + lprintf("mms: failed to read new ASF header\n"); + return 0; + } + + interp_asf_header (this); + if (!this->asf_packet_len || !this->num_stream_ids) + return 0; + + if (!mms_choose_best_streams(io, this)) + return 0; + + /* send command 0x07 */ + /* TODO: ugly */ + /* command 0x07 */ + { + mms_buffer_t command_buffer; + mms_buffer_init(&command_buffer, this->scmd_body); + mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */ + mms_buffer_put_32 (&command_buffer, 0x00000000); + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */ + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */ + mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */ + mms_buffer_put_8 (&command_buffer, 0xFF); + mms_buffer_put_8 (&command_buffer, 0xFF); + mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */ + mms_buffer_put_32 (&command_buffer, ASF_MEDIA_PACKET_ID_TYPE); /* asf media packet id type */ + if (!send_command (io, this, 0x07, 1, 0x0001FFFF, command_buffer.pos)) { + lprintf("mms: failed to send command 0x07\n"); + return 0; + } + } + this->current_pos = 0; + + /* I don't know if this ever happens with none live (and thus + seekable streams), but I do know that if it happens all bets + with regards to seeking are off */ + this->seekable = 0; + } + break; + + case 0x1b: + { + if (!send_command (io, this, 0x1b, 0, 0, 0)) { + lprintf("mms: error sending ping reply\n"); + return 0; + } + } + break; + + case 0x05: + break; + + default: + lprintf("mms: unexpected mms command %02x\n", command); + } + this->buf_size = 0; + } + break; + + case MMS_PACKET_ASF_HEADER: + lprintf("mms: unexpected asf header packet\n"); + this->buf_size = 0; + break; + + case MMS_PACKET_ASF_PACKET: + { + /* media packet */ + + /* FIXME: probably needs some more sophisticated logic, but + until we do seeking, this should work */ + if(this->need_discont && + header.packet_id_type == ASF_MEDIA_PACKET_ID_TYPE) + { + this->need_discont = 0; + this->start_packet_seq = header.packet_seq; + } + + if (header.packet_len > this->asf_packet_len) { + lprintf("mms: invalid asf packet len: %d bytes\n", header.packet_len); + return 0; + } + + /* simulate a seek */ + this->current_pos = (off_t)this->asf_header_len + + ((off_t)header.packet_seq - this->start_packet_seq) * (off_t)this->asf_packet_len; + + len = io_read(io, this->s, this->buf, header.packet_len); + if (len != header.packet_len) { + lprintf("mms: error reading asf packet\n"); + return 0; + } + + /* explicit padding with 0 */ + memset(this->buf + header.packet_len, 0, this->asf_packet_len - header.packet_len); + if (header.packet_id_type == this->packet_id_type) { + this->buf_size = this->asf_packet_len; + this->buf_packet_seq_offset = + header.packet_seq - this->start_packet_seq; + } else { + this->buf_size = 0; + // Don't set this packet sequence for reuse in seek(), since the + // subsequence packet may be discontinued. + //this->buf_packet_seq_offset = header.packet_seq; + // already set to -1 in get_packet_header + //this->buf_packet_seq_offset = -1; + } + } + break; + } + + return 1; +} + + +int mms_peek_header (mms_t *this, char *data, int maxsize) { + + int len; + + len = (this->asf_header_len < maxsize) ? this->asf_header_len : maxsize; + + memcpy(data, this->asf_header, len); + return len; +} + +int mms_read (mms_io_t *io, mms_t *this, char *data, int len) { + int total; + + total = 0; + while (total < len && !this->eos) { + + if (this->asf_header_read < this->asf_header_len) { + int n, bytes_left; + + bytes_left = this->asf_header_len - this->asf_header_read ; + + if ((len - total) < bytes_left) + n = len-total; + else + n = bytes_left; + + memcpy (&data[total], &this->asf_header[this->asf_header_read], n); + + this->asf_header_read += n; + total += n; + this->current_pos += n; + } else { + + int n, bytes_left; + + bytes_left = this->buf_size - this->buf_read; + if (bytes_left == 0) { + this->buf_size = this->buf_read = 0; + if (!get_media_packet (io, this)) { + lprintf("mms: get_media_packet failed\n"); + return total; + } + bytes_left = this->buf_size; + } + + if ((len - total) < bytes_left) + n = len - total; + else + n = bytes_left; + + memcpy (&data[total], &this->buf[this->buf_read], n); + + this->buf_read += n; + total += n; + this->current_pos += n; + } + } + return total; +} + +// To be inline function? +static int mms_request_data_packet (mms_io_t *io, mms_t *this, + double time_sec, unsigned long first_packet, unsigned long time_msec_limit) { + /* command 0x07 */ + { + mms_buffer_t command_buffer; + //mms_buffer_init(&command_buffer, this->scmd_body); + //mms_buffer_put_32 (&command_buffer, 0x00000000); /* 64 byte float timestamp */ + //mms_buffer_put_32 (&command_buffer, 0x00000000); + memcpy(this->scmd_body, &time_sec, 8); + mms_buffer_init(&command_buffer, this->scmd_body+8); + mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* ?? */ + //mms_buffer_put_32 (&command_buffer, 0xFFFFFFFF); /* first packet sequence */ + mms_buffer_put_32 (&command_buffer, first_packet); /* first packet sequence */ + //mms_buffer_put_8 (&command_buffer, 0xFF); /* max stream time limit (3 bytes) */ + //mms_buffer_put_8 (&command_buffer, 0xFF); + //mms_buffer_put_8 (&command_buffer, 0xFF); + //mms_buffer_put_8 (&command_buffer, 0x00); /* stream time limit flag */ + mms_buffer_put_32 (&command_buffer, time_msec_limit & 0x00FFFFFF);/* max stream time limit (3 bytes) */ + mms_buffer_put_32 (&command_buffer, this->packet_id_type); /* asf media packet id type */ + if (!send_command (io, this, 0x07, 1, 0x0001FFFF, 8+command_buffer.pos)) { + lprintf("mms: failed to send command 0x07\n"); + return 0; + } + } + /* TODO: adjust current_pos, considering asf_header_read */ + return 1; +} + +int mms_request_time_seek (mms_io_t *io, mms_t *this, double time_sec) { + if (++this->packet_id_type <= ASF_MEDIA_PACKET_ID_TYPE) + this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE+1; + //return mms_request_data_packet (io, this, time_sec, 0xFFFFFFFF, 0x00FFFFFF); + // also adjust time by preroll + return mms_request_data_packet (io, this, + time_sec+(double)(this->preroll)/1000, + 0xFFFFFFFF, 0x00FFFFFF); +} + +// set current_pos to the first byte of the requested packet by peeking at +// the first packet. +// To be inline function? +static int peek_and_set_pos (mms_io_t *io, mms_t *this) { + uint8_t saved_buf[BUF_SIZE]; + int saved_buf_size; + off_t saved_buf_packet_seq_offset; + // save buf and buf_size that may be changed in get_media_packet() + memcpy(saved_buf, this->buf, this->buf_size); + saved_buf_size = this->buf_size; + saved_buf_packet_seq_offset = this->buf_packet_seq_offset; + //this->buf_size = this->buf_read = 0; // reset buf, only if success peeking + this->buf_size = 0; + while (!this->eos) { + // get_media_packet() will set current_pos if data packet is read. + if (!get_media_packet (io, this)) { + lprintf("mms: get_media_packet failed\n"); + // restore buf and buf_size that may be changed in get_media_packet() + memcpy(this->buf, saved_buf, saved_buf_size); + this->buf_size = saved_buf_size; + this->buf_packet_seq_offset = saved_buf_packet_seq_offset; + return 0; + } + if (this->buf_size > 0) break; + } + // flush header and reset buf_read, only if success peeking + this->asf_header_read = this->asf_header_len; + this->buf_read = 0; + return 1; + //return this->current_pos; +} + +// send time seek request, and update current_pos corresponding to the next +// requested packet +// Note that, the current_pos will always does not less than asf_header_len +int mms_time_seek (mms_io_t *io, mms_t *this, double time_sec) { + if (!this->seekable) + return 0; + + if (!mms_request_time_seek (io, this, time_sec)) return 0; + return peek_and_set_pos (io, this); +} + +// http://sdp.ppona.com/zipfiles/MMSprotocol_pdf.zip said that, this +// packet_seq value make no difference in version 9 servers. +// But from my experiment with +// mms://202.142.200.130/tltk/56k/tltkD2006-08-08ID-7209.wmv and +// mms://202.142.200.130/tltk/56k/tltkD2006-09-01ID-7467.wmv (the url may valid +// in only 2-3 months) whose server is version 9, it does response and return +// the requested packet. +int mms_request_packet_seek (mms_io_t *io, mms_t *this, + unsigned long packet_seq) { + if (++this->packet_id_type <= ASF_MEDIA_PACKET_ID_TYPE) + this->packet_id_type = ASF_MEDIA_PACKET_ID_TYPE+1; + return mms_request_data_packet (io, this, 0, packet_seq, 0x00FFFFFF); +} + +// send packet seek request, and update current_pos corresponding to the next +// requested packet +// Note that, the current_pos will always does not less than asf_header_len +// Not export this function. Let user use mms_seek() instead? +static int mms_packet_seek (mms_io_t *io, mms_t *this, + unsigned long packet_seq) { + if (!mms_request_packet_seek (io, this, packet_seq)) return 0; + return peek_and_set_pos (io, this); +} + +/* +TODO: To use this table to calculate buf_packet_seq_offset rather than store +and retrieve it from this->buf_packet_seq_offset? +current_packet_seq == (current_pos - asf_header_len) / asf_packet_len +current_packet_seq == -1 if current_pos < asf_header_len +buf_packet_seq_offset indicating which packet sequence are residing in the buf. +Possible status after read(), "last" means last value or unchange. +current_packet_seq | buf_read | buf_size | buf_packet_seq_offset +-------------------+----------------+-----------+--------------- +<= 0 | 0 (last) | 0 (last) | none +<= 0 | 0 (last) | 0 (last) | eos at #0 +<= 0 | 0 (last) | 0 (last) | eos at > #0 +<= 0 | 0 (last) | > 0 (last)| #0 +<= 0 | buf_size (last)| > 0 (last)| > #0 +> 0 | 0 | 0 | eos at current_packet_seq +> 0 | 0(never happen)| > 0 | (never happen) +> 0 | buf_size | > 0 | current_packet_seq-1 +*/ +// TODO: How to handle seek() in multi stream source? +// The stream that follows 0x20 ("new stream") command. +off_t mms_seek (mms_io_t *io, mms_t *this, off_t offset, int origin) { + off_t dest; + off_t dest_packet_seq; + //off_t buf_packet_seq_offset; + + if (!this->seekable) + return this->current_pos; + + switch (origin) { + case SEEK_SET: + dest = offset; + break; + case SEEK_CUR: + dest = this->current_pos + offset; + break; + case SEEK_END: + //if (this->asf_num_packets == 0) { + // //printf ("input_mms: unknown end position in seek!\n"); + // return this->current_pos; + //} + dest = mms_get_length (this) + offset; + default: + printf ("input_mms: unknown origin in seek!\n"); + return this->current_pos; + } + + dest_packet_seq = dest - this->asf_header_len; + //if (dest_packet_seq > 0) dest_packet_seq /= this->asf_packet_len; + dest_packet_seq = dest_packet_seq >= 0 ? + dest_packet_seq / this->asf_packet_len : -1; +#if 0 + // buf_packet_seq_offset will identify which packet sequence are residing in + // the buf. +#if 1 /* To show both of the alternate styles :D */ + buf_packet_seq_offset = this->current_pos - this->asf_header_len; + //if (buf_packet_seq_offset > 0) buf_packet_seq_offset /= this->asf_packet_len; + buf_packet_seq_offset = buf_packet_seq_offset >= 0 ? + buf_packet_seq_offset / this->asf_packet_len : -1; + // Note: buf_read == buf_size == 0 may means that it is eos, + // eos means that the packet has been peek'ed. + if (this->buf_read >= this->buf_size && this->buf_size > 0 && + buf_packet_seq_offset >= 0 || + // assuming packet not peek'ed in the following condition + /*this->buf_read >= this->buf_size && */this->buf_size == 0 && + buf_packet_seq_offset == 0) + // The buf is all read but the packet has not been peek'ed. + --buf_packet_seq_offset; +#else + buf_packet_seq_offset = this->current_pos - this->asf_header_len - 1; + //if (buf_packet_seq_offset > 0) buf_packet_seq_offset /= this->asf_packet_len; + buf_packet_seq_offset = buf_packet_seq_offset >= 0 ? + buf_packet_seq_offset / this->asf_packet_len : -1; + // Note: buf_read == buf_size == 0 may means that it is eos, + // eos means that the packet has been peek'ed. + if (this->buf_read == 0/* && buf_packet_seq_offset >= 0*/) + // Since the packet has just been peek'ed. + ++buf_packet_seq_offset; +#endif +#endif + + if (dest_packet_seq < 0) { + if (this->buf_packet_seq_offset > 0) { + if (!mms_request_packet_seek (io, this, 0xFFFFFFFF)) + return this->current_pos; +#if 1 + // clear buf + this->buf_read = this->buf_size = 0; + this->buf_packet_seq_offset = -1; + } else { +#else + // clear buf + this->buf_read = this->buf_size; + // Set this packet sequence not to be reused, since the subsequence + // packet may be discontinued. + this->buf_packet_seq_offset = -1; + // don't reset buf_read if buf_packet_seq_offset < 0, since the previous + // buf may not be cleared. + } else if (this->buf_packet_seq_offset == 0) { +#endif + // reset buf_read + this->buf_read = 0; + } + this->asf_header_read = dest; + return this->current_pos = dest; + } + // dest_packet_seq >= 0 + if (this->asf_num_packets && dest == this->asf_header_len + + this->asf_num_packets*this->asf_packet_len) { + // Requesting the packet beyond the last packet, can cause the server to + // not return any packet or any eos command. This can cause + // mms_packet_seek() to hang. + // This is to allow seeking at end of stream, and avoid hanging. + --dest_packet_seq; + } + if (dest_packet_seq != this->buf_packet_seq_offset) { + if (this->asf_num_packets && dest_packet_seq >= this->asf_num_packets) { + // Do not seek beyond the last packet. + return this->current_pos; + } + if (!mms_packet_seek (io, this, this->start_packet_seq + dest_packet_seq)) + return this->current_pos; + // Check if current_pos is correct. + // This can happen if the server ignore packet seek command. + // If so, just return unchanged current_pos, rather than trying to + // mms_read() to reach the destination pos. + // It should let the caller to decide to choose the alternate method, such + // as, mms_time_seek() and/or mms_read() until the destination pos is + // reached. + if (dest_packet_seq != this->buf_packet_seq_offset) + return this->current_pos; + // This has already been set in mms_packet_seek(). + //if (current_packet_seq < 0) + // this->asf_header_read = this->asf_header_len; + //this->asf_header_read = this->asf_header_len; + } + // eos is reached ? + //if (this->buf_size <= 0) return this->current_pos; + //this->buf_read = (dest - this->asf_header_len) % this->asf_packet_len; + this->buf_read = dest - + (this->asf_header_len + dest_packet_seq*this->asf_packet_len); + // will never happen ? + //if (this->buf_size <= this->buf_read) return this->current_pos; + return this->current_pos = dest; +} + + +void mms_close (mms_t *this) { + + if (this->s != -1) + close (this->s); + if (this->url) + free(this->url); + if (this->guri) + gnet_uri_delete(this->guri); + if (this->uri) + free(this->uri); + + free (this); +} + +double mms_get_time_length (mms_t *this) { + return (double)(this->time_len) / 1e7; +} + +uint64_t mms_get_raw_time_length (mms_t *this) { + return this->time_len; +} + +uint32_t mms_get_length (mms_t *this) { + /* we could / should return this->file_len here, but usually this->file_len + is longer then the calculation below, as usually an asf file contains an + asf index object after the data stream. However since we do not have a + (known) way to get to this index object through mms, we return a + calculated size of what we can get to when we know. */ + if (this->asf_num_packets) + return this->asf_header_len + this->asf_num_packets*this->asf_packet_len; + else + return this->file_len; +} + +off_t mms_get_current_pos (mms_t *this) { + return this->current_pos; +} + +uint32_t mms_get_asf_header_len (mms_t *this) { + return this->asf_header_len; +} + +uint64_t mms_get_asf_packet_len (mms_t *this) { + return this->asf_packet_len; +} + +int mms_get_seekable (mms_t *this) { + return this->seekable; +} diff --git a/plugins/mms/libmms/mms.h b/plugins/mms/libmms/mms.h new file mode 100644 index 00000000..bce71d54 --- /dev/null +++ b/plugins/mms/libmms/mms.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2002-2003 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can 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. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 + * + * $Id: mms.h,v 1.15 2007/12/11 20:24:48 jwrdegoede Exp $ + * + * libmms public header + */ + +#ifndef HAVE_MMS_H +#define HAVE_MMS_H + +#include <inttypes.h> +#include <stdio.h> +#include <sys/types.h> + +#include "mmsio.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct mms_s mms_t; + +mms_t* mms_connect (mms_io_t *io, void *data, const char *url, int bandwidth); + +int mms_read (mms_io_t *io, mms_t *instance, char *data, int len); +int mms_request_time_seek (mms_io_t *io, mms_t *instance, double time_sec); +int mms_time_seek (mms_io_t *io, mms_t *instance, double time_sec); +int mms_request_packet_seek (mms_io_t *io, mms_t *instance, + unsigned long packet_seq); +/* + * mms_seek() will try to seek using mms_request_packet_seek(), if the server + * ignore the packet seek command, it will return unchanged current_pos, rather + * than trying to mms_read() until the destination pos is reached. This is to + * let the caller, by itself, to decide to choose the alternate method, such + * as, mms_time_seek() and/or mms_read() until the destination pos is reached. + * One can do binary search using time offset (mms_time_seek()) as a search + * index, to approach the desired byte offset. It is to systematically guess + * the time offset to reach for the byte offset. + */ +mms_off_t mms_seek (mms_io_t *io, mms_t *instance, mms_off_t offset, int origin); +/* return total playback time in seconds */ +double mms_get_time_length (mms_t *instance); +/* return raw total playback time in 100 nanosecs (10^-7) */ +uint64_t mms_get_raw_time_length (mms_t *instance); +uint32_t mms_get_length (mms_t *instance); +void mms_close (mms_t *instance); + +int mms_peek_header (mms_t *instance, char *data, int maxsize); + +mms_off_t mms_get_current_pos (mms_t *instance); + +uint32_t mms_get_asf_header_len (mms_t *instance); + +uint64_t mms_get_asf_packet_len (mms_t *instance); + +int mms_get_seekable (mms_t *instance); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + diff --git a/plugins/mms/libmms/mmsh.c b/plugins/mms/libmms/mmsh.c new file mode 100644 index 00000000..9a7b119b --- /dev/null +++ b/plugins/mms/libmms/mmsh.c @@ -0,0 +1,1534 @@ +/* + * Copyright (C) 2002-2003 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can 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. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 + * + * $Id: mmsh.c,v 1.16 2007/12/11 20:50:43 jwrdegoede Exp $ + * + * MMS over HTTP protocol + * written by Thibaut Mattern + * based on mms.c and specs from avifile + * (http://avifile.sourceforge.net/asf-1.0.htm) + * + * TODO: + * error messages + * http support cleanup, find a way to share code with input_http.c (http.h|c) + * http proxy support + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <stdlib.h> +#include <time.h> +#include <assert.h> + +#define lprintf(...) if (getenv("LIBMMS_DEBUG")) fprintf(stderr, __VA_ARGS__) + +/* cheat a bit and call ourselves mms.c to keep the code in mmsio.h clean */ +#define __MMS_C__ + +#include "bswap.h" +#include "mmsh.h" +#include "asfheader.h" +#include "uri.h" +#include "mms-common.h" + +/* #define USERAGENT "User-Agent: NSPlayer/7.1.0.3055\r\n" */ +#define USERAGENT "User-Agent: NSPlayer/4.1.0.3856\r\n" +#define CLIENTGUID "Pragma: xClientGUID={c77e7400-738a-11d2-9add-0020af0a3278}\r\n" + + +#define MMSH_PORT 80 +#define MMSH_UNKNOWN 0 +#define MMSH_SEEKABLE 1 +#define MMSH_LIVE 2 + +#define CHUNK_HEADER_LENGTH 4 +#define EXT_HEADER_LENGTH 8 +#define CHUNK_TYPE_RESET 0x4324 +#define CHUNK_TYPE_DATA 0x4424 +#define CHUNK_TYPE_END 0x4524 +#define CHUNK_TYPE_ASF_HEADER 0x4824 +#define CHUNK_SIZE 65536 /* max chunk size */ +#define ASF_HEADER_SIZE (8192 * 2) /* max header size */ + +#define SCRATCH_SIZE 1024 + +#define SUCCESS 0 +#define ERROR 1 +#define EOS 2 +#define GOT_HEADER_N_DATA 3 + +static const char* mmsh_FirstRequest = + "GET %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s:%d\r\n" + "Pragma: no-cache,rate=1.000000,stream-time=0,stream-offset=0:0,request-context=%u,max-duration=0\r\n" + CLIENTGUID + "Connection: Close\r\n\r\n"; + +static const char* mmsh_SeekableRequest = + "GET %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s:%d\r\n" + "Pragma: no-cache,rate=1.000000,stream-time=%u,stream-offset=%u:%u,request-context=%u,max-duration=%u\r\n" + CLIENTGUID + "Pragma: xPlayStrm=1\r\n" + "Pragma: stream-switch-count=%d\r\n" + "Pragma: stream-switch-entry=%s\r\n" /* ffff:1:0 ffff:2:0 */ + "Connection: Close\r\n\r\n"; + +static const char* mmsh_LiveRequest = + "GET %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s:%d\r\n" + "Pragma: no-cache,rate=1.000000,request-context=%u\r\n" + "Pragma: xPlayStrm=1\r\n" + CLIENTGUID + "Pragma: stream-switch-count=%d\r\n" + "Pragma: stream-switch-entry=%s\r\n" + "Connection: Close\r\n\r\n"; + + +#if 0 +/* Unused requests */ +static const char* mmsh_PostRequest = + "POST %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s\r\n" + "Pragma: client-id=%u\r\n" +/* "Pragma: log-line=no-cache,rate=1.000000,stream-time=%u,stream-offset=%u:%u,request-context=2,max-duration=%u\r\n" */ + "Pragma: Content-Length: 0\r\n" + CLIENTGUID + "\r\n"; + +static const char* mmsh_RangeRequest = + "GET %s HTTP/1.0\r\n" + "Accept: */*\r\n" + USERAGENT + "Host: %s:%d\r\n" + "Range: bytes=%Lu-\r\n" + CLIENTGUID + "Connection: Close\r\n\r\n"; +#endif + + + +/* + * mmsh specific types + */ + + +struct mmsh_s { + + int s; + + /* url parsing */ + char *url; + char *proxy_url; + char *proto; + char *connect_host; + int connect_port; + char *http_host; + int http_port; + int http_request_number; + char *proxy_user; + char *proxy_password; + char *host_user; + char *host_password; + char *uri; + + char str[SCRATCH_SIZE]; /* scratch buffer to built strings */ + + int stream_type; /* seekable or broadcast */ + + /* receive buffer */ + + /* chunk */ + uint16_t chunk_type; + uint16_t chunk_length; + uint32_t chunk_seq_number; + uint8_t buf[CHUNK_SIZE]; + + int buf_size; + int buf_read; + + uint8_t asf_header[ASF_HEADER_SIZE]; + uint32_t asf_header_len; + uint32_t asf_header_read; + int num_stream_ids; + mms_stream_t streams[ASF_MAX_NUM_STREAMS]; + uint32_t packet_length; + int64_t file_length; + uint64_t time_len; /* playback time in 100 nanosecs (10^-7) */ + uint64_t preroll; + uint64_t asf_num_packets; + char guid[37]; + + int has_audio; + int has_video; + int seekable; + + off_t current_pos; + int user_bandwidth; +}; + +static int fallback_io_select(void *data, int socket, int state, int timeout_msec) +{ + fd_set set; + struct timeval tv = { timeout_msec / 1000, (timeout_msec % 1000) * 1000}; + FD_ZERO(&set); + FD_SET(socket, &set); + return select(1, (state == MMS_IO_READ_READY) ? &set : NULL, + (state == MMS_IO_WRITE_READY) ? &set : NULL, NULL, &tv); +} + +static off_t fallback_io_read(void *data, int socket, char *buf, off_t num) +{ + off_t len = 0, ret; +/* lprintf("%d\n", fallback_io_select(data, socket, MMS_IO_READ_READY, 1000)); */ + errno = 0; + while (len < num) + { + ret = (off_t)read(socket, buf + len, num - len); + if(ret == 0) + break; /* EOS */ + if(ret < 0) { + lprintf("mmsh: read error @ len = %lld: %s\n", (long long int) len, + strerror(errno)); + switch(errno) + { + case EAGAIN: + continue; + default: + /* if already read something, return it, we will fail next time */ + return len ? len : ret; + } + } + len += ret; + } + return len; +} + +static off_t fallback_io_write(void *data, int socket, char *buf, off_t num) +{ + return (off_t)write(socket, buf, num); +} + +static int fallback_io_tcp_connect(void *data, const char *host, int port) +{ + + struct hostent *h; + int i, s; + + h = gethostbyname(host); + if (h == NULL) { + lprintf("mmsh: unable to resolve host: %s\n", host); + return -1; + } + + s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (s == -1) { + lprintf("mmsh: failed to create socket: %s\n", strerror(errno)); + return -1; + } + + if (fcntl (s, F_SETFL, fcntl (s, F_GETFL) & ~O_NONBLOCK) == -1) { + lprintf("mmsh: failed to set socket flags: %s\n", strerror(errno)); + return -1; + } + + for (i = 0; h->h_addr_list[i]; i++) { + struct in_addr ia; + struct sockaddr_in sin; + + memcpy (&ia, h->h_addr_list[i], 4); + sin.sin_family = AF_INET; + sin.sin_addr = ia; + sin.sin_port = htons(port); + + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) ==-1 && errno != EINPROGRESS) { + continue; + } + + return s; + } + close(s); + return -1; +} + + +static mms_io_t fallback_io = + { + &fallback_io_select, + NULL, + &fallback_io_read, + NULL, + &fallback_io_write, + NULL, + &fallback_io_tcp_connect, + NULL, + }; + +static mms_io_t default_io = { + &fallback_io_select, + NULL, + &fallback_io_read, + NULL, + &fallback_io_write, + NULL, + &fallback_io_tcp_connect, + NULL, + }; + + +#define io_read(io, args...) ((io) ? (io)->read(io->read_data , ## args) : default_io.read(NULL , ## args)) +#define io_write(io, args...) ((io) ? (io)->write(io->write_data , ## args) : default_io.write(NULL , ## args)) +#define io_select(io, args...) ((io) ? (io)->select(io->select_data , ## args) : default_io.select(NULL , ## args)) +#define io_connect(io, args...) ((io) ? (io)->connect(io->connect_data , ## args) : default_io.connect(NULL , ## args)) + +static int get_guid (unsigned char *buffer, int offset) { + int i; + GUID g; + + g.Data1 = LE_32(buffer + offset); + g.Data2 = LE_16(buffer + offset + 4); + g.Data3 = LE_16(buffer + offset + 6); + for(i = 0; i < 8; i++) { + g.Data4[i] = buffer[offset + 8 + i]; + } + + for (i = 1; i < GUID_END; i++) { + if (!memcmp(&g, &guids[i].guid, sizeof(GUID))) { + lprintf("mmsh: GUID: %s\n", guids[i].name); + return i; + } + } + + lprintf("mmsh: unknown GUID: 0x%x, 0x%x, 0x%x, " + "{ 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx, 0x%hx }\n", + g.Data1, g.Data2, g.Data3, + g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], + g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]); + return GUID_ERROR; +} + +static int send_command (mms_io_t *io, mmsh_t *this, char *cmd) { + int length; + + lprintf("mmsh: send_command:\n%s\n", cmd); + + length = strlen(cmd); + if (io_write(io, this->s, cmd, length) != length) { + lprintf ("mmsh: send error.\n"); + return 0; + } + return 1; +} + +static int get_answer (mms_io_t *io, mmsh_t *this) { + + int done, len, linenum; + char *features; + + done = 0; len = 0; linenum = 0; + this->stream_type = MMSH_UNKNOWN; + + while (!done) { + + if (io_read(io, this->s, &(this->buf[len]), 1) != 1) { + lprintf ("mmsh: alart: end of stream\n"); + return 0; + } + + if (this->buf[len] == '\012') { + + this->buf[len] = '\0'; + len--; + + if ((len >= 0) && (this->buf[len] == '\015')) { + this->buf[len] = '\0'; + len--; + } + + linenum++; + + lprintf("mmsh: answer: >%s<\n", this->buf); + + if (linenum == 1) { + int httpver, httpsub, httpcode; + char httpstatus[51]; + + if (sscanf(this->buf, "HTTP/%d.%d %d %50[^\015\012]", &httpver, &httpsub, + &httpcode, httpstatus) != 4) { + lprintf ("mmsh: bad response format\n"); + return 0; + } + + if (httpcode >= 300 && httpcode < 400) { + lprintf ("mmsh: 3xx redirection not implemented: >%d %s<\n", httpcode, httpstatus); + return 0; + } + + if (httpcode < 200 || httpcode >= 300) { + lprintf ("mmsh: http status not 2xx: >%d %s<\n", httpcode, httpstatus); + return 0; + } + } else { + + if (!strncasecmp(this->buf, "Location: ", 10)) { + lprintf ("mmsh: Location redirection not implemented.\n"); + return 0; + } + + if (!strncasecmp(this->buf, "Pragma:", 7)) { + features = strstr(this->buf + 7, "features="); + if (features) { + if (strstr(features, "seekable")) { + lprintf("mmsh: seekable stream\n"); + this->stream_type = MMSH_SEEKABLE; + this->seekable = 1; + } else { + if (strstr(features, "broadcast")) { + lprintf("mmsh: live stream\n"); + this->stream_type = MMSH_LIVE; + this->seekable = 0; + } + } + } + } + } + + if (len == -1) { + done = 1; + } else { + len = 0; + } + } else { + len ++; + } + } + if (this->stream_type == MMSH_UNKNOWN) { + lprintf ("mmsh: unknown stream type\n"); + this->stream_type = MMSH_SEEKABLE; /* FIXME ? */ + this->seekable = 1; + } + return 1; +} + +static int get_chunk_header (mms_io_t *io, mmsh_t *this) { + uint8_t chunk_header[CHUNK_HEADER_LENGTH]; + uint8_t ext_header[EXT_HEADER_LENGTH]; + int read_len; + int ext_header_len; + + /* read chunk header */ + read_len = io_read(io, this->s, chunk_header, CHUNK_HEADER_LENGTH); + if (read_len != CHUNK_HEADER_LENGTH) { + if (read_len == 0) + return EOS; + lprintf("mmsh: chunk header read failed, %d != %d\n", read_len, CHUNK_HEADER_LENGTH); + return ERROR; + } + this->chunk_type = LE_16 (&chunk_header[0]); + this->chunk_length = LE_16 (&chunk_header[2]); + + switch (this->chunk_type) { + case CHUNK_TYPE_DATA: + ext_header_len = 8; + break; + case CHUNK_TYPE_END: + ext_header_len = 4; + break; + case CHUNK_TYPE_ASF_HEADER: + ext_header_len = 8; + break; + case CHUNK_TYPE_RESET: + ext_header_len = 4; + break; + default: + ext_header_len = 0; + } + /* read extended header */ + if (ext_header_len > 0) { + read_len = io_read (io, this->s, ext_header, ext_header_len); + if (read_len != ext_header_len) { + lprintf("mmsh: extended header read failed. %d != %d\n", read_len, ext_header_len); + return ERROR; + } + } + + if (this->chunk_type == CHUNK_TYPE_DATA || this->chunk_type == CHUNK_TYPE_END) + this->chunk_seq_number = LE_32 (&ext_header[0]); + + /* display debug infos */ +#ifdef DEBUG + switch (this->chunk_type) { + case CHUNK_TYPE_DATA: + lprintf ("chunk type: CHUNK_TYPE_DATA\n"); + lprintf ("chunk length: %d\n", this->chunk_length); + lprintf ("chunk seq: %d\n", this->chunk_seq_number); + lprintf ("unknown: %d\n", ext_header[4]); + lprintf ("mmsh seq: %d\n", ext_header[5]); + lprintf ("len2: %d\n", LE_16(&ext_header[6])); + break; + case CHUNK_TYPE_END: + lprintf ("chunk type: CHUNK_TYPE_END\n"); + lprintf ("continue: %d\n", this->chunk_seq_number); + break; + case CHUNK_TYPE_ASF_HEADER: + lprintf ("chunk type: CHUNK_TYPE_ASF_HEADER\n"); + lprintf ("chunk length: %d\n", this->chunk_length); + lprintf ("unknown: %2X %2X %2X %2X %2X %2X\n", + ext_header[0], ext_header[1], ext_header[2], ext_header[3], + ext_header[4], ext_header[5]); + lprintf ("len2: %d\n", LE_16(&ext_header[6])); + break; + case CHUNK_TYPE_RESET: + lprintf ("chunk type: CHUNK_TYPE_RESET\n"); + lprintf ("chunk seq: %d\n", this->chunk_seq_number); + lprintf ("unknown: %2X %2X %2X %2X\n", + ext_header[0], ext_header[1], ext_header[2], ext_header[3]); + break; + default: + lprintf ("unknown chunk: %4X\n", this->chunk_type); + } +#endif + + this->chunk_length -= ext_header_len; + return SUCCESS; +} + +static int get_header (mms_io_t *io, mmsh_t *this) { + int ret, len = 0; + + this->asf_header_len = 0; + this->asf_header_read = 0; + this->buf_size = 0; + + /* read chunk */ + while (1) { + if ((ret = get_chunk_header(io, this)) == SUCCESS) { + if (this->chunk_type == CHUNK_TYPE_ASF_HEADER) { + if ((this->asf_header_len + this->chunk_length) > ASF_HEADER_SIZE) { + lprintf ("mmsh: the asf header exceed %d bytes\n", ASF_HEADER_SIZE); + return ERROR; + } else { + len = io_read(io, this->s, this->asf_header + this->asf_header_len, + this->chunk_length); + if (len > 0) + this->asf_header_len += len; + if (len != this->chunk_length) { + lprintf ("mmsh: asf header chunk read failed, %d != %d\n", len, + this->chunk_length); + return ERROR; + } + } + } else { + break; + } + } else { + if (this->asf_header_len == 0 || ret != EOS) + lprintf("mmsh: get_header failed to get chunk header\n"); + return ret; + } + } + + if (this->chunk_type == CHUNK_TYPE_DATA) { + /* read the first data chunk */ + len = io_read (io, this->s, this->buf, this->chunk_length); + + if (len != this->chunk_length) { + lprintf ("mmsh: asf data chunk read failed, %d != %d\n", len, + this->chunk_length); + return ERROR; + } else { + /* check and 0 pad the first data chunk */ + if (this->chunk_length > this->packet_length) { + lprintf ("mmsh: chunk_length(%d) > packet_length(%d)\n", + this->chunk_length, this->packet_length); + return ERROR; + } + + /* explicit padding with 0 */ + if (this->chunk_length < this->packet_length) + memset(this->buf + this->chunk_length, 0, + this->packet_length - this->chunk_length); + + this->buf_size = this->packet_length; + + return SUCCESS; + } + } else { + /* unexpected packet type */ + lprintf ("mmsh: unexpected chunk_type(0x%04x)\n", this->chunk_type); + return ERROR; + } +} + +static void interp_stream_properties(mmsh_t *this, int i) { + uint16_t flags; + uint16_t stream_id; + int type; + int encrypted; + int guid; + + guid = get_guid(this->asf_header, i); + switch (guid) { + case GUID_ASF_AUDIO_MEDIA: + type = ASF_STREAM_TYPE_AUDIO; + this->has_audio = 1; + break; + + case GUID_ASF_VIDEO_MEDIA: + case GUID_ASF_JFIF_MEDIA: + case GUID_ASF_DEGRADABLE_JPEG_MEDIA: + type = ASF_STREAM_TYPE_VIDEO; + this->has_video = 1; + break; + + case GUID_ASF_COMMAND_MEDIA: + type = ASF_STREAM_TYPE_CONTROL; + break; + + default: + type = ASF_STREAM_TYPE_UNKNOWN; + } + + flags = LE_16(this->asf_header + i + 48); + stream_id = flags & 0x7F; + encrypted = flags >> 15; + + lprintf("mmsh: stream object, stream id: %d, type: %d, encrypted: %d\n", + stream_id, type, encrypted); + + if (this->num_stream_ids >= ASF_MAX_NUM_STREAMS) { + lprintf("mmsh: too many streams, skipping\n"); + return; + } + + this->streams[this->num_stream_ids].stream_type = type; + this->streams[this->num_stream_ids].stream_id = stream_id; + this->num_stream_ids++; +} + +static void interp_header (mms_io_t *io, mmsh_t *this) { + + int i; + + this->packet_length = 0; + this->num_stream_ids = 0; + this->asf_num_packets = 0; + + /* + * parse asf header + */ + + i = 30; + while ((i + 24) <= this->asf_header_len) { + + int guid; + uint64_t length; + + guid = get_guid(this->asf_header, i); + length = LE_64(this->asf_header + i + 16); + + if ((i + length) > this->asf_header_len) return; + + switch (guid) { + + case GUID_ASF_FILE_PROPERTIES: + + this->packet_length = LE_32(this->asf_header + i + 92); + if (this->packet_length > CHUNK_SIZE) { + this->packet_length = 0; + break; + } + this->file_length = LE_64(this->asf_header + i + 40); + this->time_len = LE_64(this->asf_header + i + 64); + //this->time_len = LE_64(this->asf_header + i + 72); + this->preroll = LE_64(this->asf_header + i + 80); + lprintf("mmsh: file object, packet length = %d (%d)\n", + this->packet_length, LE_32(this->asf_header + i + 96)); + break; + + case GUID_ASF_STREAM_PROPERTIES: + interp_stream_properties(this, i + 24); + break; + + case GUID_ASF_STREAM_BITRATE_PROPERTIES: + { + uint16_t streams = LE_16(this->asf_header + i + 24); + uint16_t stream_id; + int j, stream_index; + + for(j = 0; j < streams; j++) { + stream_id = LE_16(this->asf_header + i + 24 + 2 + j * 6); + + for(stream_index = 0; stream_index < this->num_stream_ids; stream_index++) { + if (this->streams[stream_index].stream_id == stream_id) + break; + } + if (stream_index < this->num_stream_ids) { + this->streams[stream_index].bitrate = LE_32(this->asf_header + i + 24 + 4 + j * 6); + this->streams[stream_index].bitrate_pos = i + 24 + 4 + j * 6; + lprintf ("mmsh: stream id %d, bitrate %d\n", stream_id, + this->streams[stream_index].bitrate); + } else + lprintf ("mmsh: unknown stream id %d in bitrate properties\n", + stream_id); + } + } + break; + + case GUID_ASF_HEADER_EXTENSION: + { + if ((24 + 18 + 4) > length) + break; + + int size = LE_32(this->asf_header + i + 24 + 18); + int j = 24 + 18 + 4; + int l; + lprintf("mmsh: Extension header data size: %d\n", size); + + while ((j + 24) <= length) { + guid = get_guid(this->asf_header, i + j); + l = LE_64(this->asf_header + i + j + 16); + + if ((j + l) > length) + break; + + if (guid == GUID_ASF_EXTENDED_STREAM_PROPERTIES && + (24 + 64) <= l) { + int stream_no = LE_16(this->asf_header + i + j + 24 + 48); + int name_count = LE_16(this->asf_header + i + j + 24 + 60); + int ext_count = LE_16(this->asf_header + i + j + 24 + 62); + int ext_j = 24 + 64; + int x; + + lprintf("mmsh: l: %d\n", l); + lprintf("mmsh: Stream No: %d\n", stream_no); + lprintf("mmsh: ext_count: %d\n", ext_count); + + // Loop through the number of stream names + for (x = 0; x < name_count && (ext_j + 4) <= l; x++) { + int lang_id_index; + int stream_name_len; + + lang_id_index = LE_16(this->asf_header + i + j + ext_j); + ext_j += 2; + + stream_name_len = LE_16(this->asf_header + i + j + ext_j); + ext_j += stream_name_len + 2; + + lprintf("mmsh: Language id index: %d\n", lang_id_index); + lprintf("mmsh: Stream name Len: %d\n", stream_name_len); + } + + // Loop through the number of extension system info + for (x = 0; x < ext_count && (ext_j + 22) <= l; x++) { + ext_j += 18; + int len = LE_16(this->asf_header + i + j + ext_j); + ext_j += 4 + len; + } + + lprintf("mmsh: ext_j: %d\n", ext_j); + // Finally, we arrive at the interesting point: The optional Stream Property Object + if ((ext_j + 24) <= l) { + guid = get_guid(this->asf_header, i + j + ext_j); + int len = LE_64(this->asf_header + i + j + ext_j + 16); + if (guid == GUID_ASF_STREAM_PROPERTIES && + (ext_j + len) <= l) { + interp_stream_properties(this, i + j + ext_j + 24); + } + } else { + lprintf("mmsh: Sorry, field not long enough\n"); + } + } + j += l; + } + } + break; + + case GUID_ASF_DATA: + this->asf_num_packets = LE_64(this->asf_header + i + 40 - 24); + lprintf("mmsh: num_packets: %d\n", (int)this->asf_num_packets); + break; + } + + lprintf("mmsh: length: %llu\n", (unsigned long long)length); + i += length; + } +} + +const static char *const mmsh_proto_s[] = { "mms", "mmsh", NULL }; + +static int mmsh_valid_proto (char *proto) { + int i = 0; + + if (!proto) + return 0; + + while(mmsh_proto_s[i]) { + if (!strcasecmp(proto, mmsh_proto_s[i])) { + return 1; + } + i++; + } + return 0; +} + +/* + * returns 1 on error + */ +static int mmsh_tcp_connect(mms_io_t *io, mmsh_t *this) { + if (!this->connect_port) this->connect_port = MMSH_PORT; + + /* + * try to connect + */ + lprintf("mmsh: try to connect to %s on port %d \n", this->connect_host, this->connect_port); + + this->s = io_connect (io, this->connect_host, this->connect_port); + + if (this->s == -1) { + lprintf("mmsh: failed to connect '%s'\n", this->connect_host); + return 1; + } + + lprintf("mmsh: connected\n"); + + return 0; +} + +static int mmsh_connect_int (mms_io_t *io, mmsh_t *this, off_t seek, uint32_t time_seek) { + int i; + int video_stream = -1; + int audio_stream = -1; + int max_arate = -1; + int min_vrate = -1; + int min_bw_left = 0; + int bandwitdh_left; + char stream_selection[10 * ASF_MAX_NUM_STREAMS]; /* 10 chars per stream */ + int offset; + + /* Close exisiting connection (if any) and connect */ + if (this->s != -1) + close(this->s); + + if (mmsh_tcp_connect(io, this)) { + return 0; + } + + /* + * let the negotiations begin... + */ + this->num_stream_ids = 0; + + /* first request */ + lprintf("mmsh: first http request\n"); + + snprintf (this->str, SCRATCH_SIZE, mmsh_FirstRequest, this->uri, + this->http_host, this->http_port, this->http_request_number++); + + if (!send_command (io, this, this->str)) + goto fail; + + if (!get_answer (io, this)) + goto fail; + + /* Don't check for != SUCCESS as EOS is normal here too */ + if (get_header(io, this) == ERROR) + goto fail; + + interp_header(io, this); + if (!this->packet_length || !this->num_stream_ids) + goto fail; + + close(this->s); + + /* choose the best quality for the audio stream */ + /* i've never seen more than one audio stream */ + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_AUDIO: + if ((audio_stream == -1) || (this->streams[i].bitrate > max_arate)) { + audio_stream = this->streams[i].stream_id; + max_arate = this->streams[i].bitrate; + } + break; + default: + break; + } + } + + /* choose a video stream adapted to the user bandwidth */ + bandwitdh_left = this->user_bandwidth - max_arate; + if (bandwitdh_left < 0) { + bandwitdh_left = 0; + } + lprintf("mmsh: bandwitdh %d, left %d\n", this->user_bandwidth, bandwitdh_left); + + min_bw_left = bandwitdh_left; + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_VIDEO: + if (((bandwitdh_left - this->streams[i].bitrate) < min_bw_left) && + (bandwitdh_left >= this->streams[i].bitrate)) { + video_stream = this->streams[i].stream_id; + min_bw_left = bandwitdh_left - this->streams[i].bitrate; + } + break; + default: + break; + } + } + + /* choose the stream with the lower bitrate */ + if ((video_stream == -1) && this->has_video) { + for (i = 0; i < this->num_stream_ids; i++) { + switch (this->streams[i].stream_type) { + case ASF_STREAM_TYPE_VIDEO: + if ((video_stream == -1) || + (this->streams[i].bitrate < min_vrate) || + (!min_vrate)) { + video_stream = this->streams[i].stream_id; + min_vrate = this->streams[i].bitrate; + } + break; + default: + break; + } + } + } + + lprintf("mmsh: audio stream %d, video stream %d\n", audio_stream, video_stream); + + /* second request */ + lprintf("mmsh: second http request\n"); + + if (mmsh_tcp_connect(io, this)) { + return 0; + } + + /* stream selection string */ + /* The same selection is done with mmst */ + /* 0 means selected */ + /* 2 means disabled */ + offset = 0; + for (i = 0; i < this->num_stream_ids; i++) { + int size; + if ((this->streams[i].stream_id == audio_stream) || + (this->streams[i].stream_id == video_stream)) { + size = snprintf(stream_selection + offset, sizeof(stream_selection) - offset, + "ffff:%d:0 ", this->streams[i].stream_id); + } else { + lprintf("mmsh: disabling stream %d\n", this->streams[i].stream_id); + size = snprintf(stream_selection + offset, sizeof(stream_selection) - offset, + "ffff:%d:2 ", this->streams[i].stream_id); + } + if (size < 0) goto fail; + offset += size; + } + + switch (this->stream_type) { + case MMSH_SEEKABLE: + snprintf (this->str, SCRATCH_SIZE, mmsh_SeekableRequest, this->uri, + this->http_host, this->http_port, time_seek, + (unsigned int)(seek >> 32), + (unsigned int)seek, this->http_request_number++, 0, + this->num_stream_ids, stream_selection); + break; + case MMSH_LIVE: + snprintf (this->str, SCRATCH_SIZE, mmsh_LiveRequest, this->uri, + this->http_host, this->http_port, this->http_request_number++, + this->num_stream_ids, stream_selection); + break; + } + + if (!send_command (io, this, this->str)) + goto fail; + + if (!get_answer (io, this)) + goto fail; + + if (get_header(io, this) != SUCCESS) + goto fail; + + interp_header(io, this); + if (!this->packet_length || !this->num_stream_ids) + goto fail; + + for (i = 0; i < this->num_stream_ids; i++) { + if ((this->streams[i].stream_id != audio_stream) && + (this->streams[i].stream_id != video_stream)) { + lprintf("disabling stream %d\n", this->streams[i].stream_id); + + /* forces the asf demuxer to not choose this stream */ + if (this->streams[i].bitrate_pos) { + if (this->streams[i].bitrate_pos + 3 < ASF_HEADER_SIZE) { + this->asf_header[this->streams[i].bitrate_pos] = 0; + this->asf_header[this->streams[i].bitrate_pos + 1] = 0; + this->asf_header[this->streams[i].bitrate_pos + 2] = 0; + this->asf_header[this->streams[i].bitrate_pos + 3] = 0; + } else { + lprintf("mmsh: attempt to write beyond asf header limit"); + } + } + } + } + return 1; +fail: + close(this->s); + this->s = -1; + return 0; +} + +mmsh_t *mmsh_connect (mms_io_t *io, void *data, const char *url, int bandwidth) { + mmsh_t *this; + GURI *uri = NULL; + GURI *proxy_uri = NULL; + char *proxy_env; + if (!url) + return NULL; + + /* + * initializatoin is essential here. the fail: label depends + * on the various char * in our this structure to be + * NULL if they haven't been assigned yet. + */ + this = (mmsh_t*) malloc (sizeof (mmsh_t)); + this->url=NULL; + this->proxy_url = NULL; + this->proto = NULL; + this->connect_host = NULL; + this->http_host = NULL; + this->proxy_user = NULL; + this->proxy_password = NULL; + this->host_user = NULL; + this->host_password = NULL; + this->uri = NULL; + + this->url = strdup(url); + if ((proxy_env = getenv("http_proxy")) != NULL) + this->proxy_url = strdup(proxy_env); + else + this->proxy_url = NULL; + this->s = -1; + this->asf_header_len = 0; + this->asf_header_read = 0; + this->num_stream_ids = 0; + this->packet_length = 0; + this->buf_size = 0; + this->buf_read = 0; + this->has_audio = 0; + this->has_video = 0; + this->current_pos = 0; + this->user_bandwidth = bandwidth; + this->http_request_number = 1; + + if (this->proxy_url) { + proxy_uri = gnet_uri_new(this->proxy_url); + if (!proxy_uri) { + lprintf("mmsh: invalid proxy url\n"); + goto fail; + } + if (! proxy_uri->port ) { + proxy_uri->port = 3128; //default squid port + } + } + uri = gnet_uri_new(this->url); + if (!uri) { + lprintf("mmsh: invalid url\n"); + goto fail; + } + if (! uri->port ) { + //checked in tcp_connect, but it's better to initialize it here + uri->port = MMSH_PORT; + } + if (this->proxy_url) { + this->proto = (uri->scheme) ? strdup(uri->scheme) : NULL; + this->connect_host = (proxy_uri->hostname) ? strdup(proxy_uri->hostname) : NULL; + this->connect_port = proxy_uri->port; + this->http_host = (uri->scheme) ? strdup(uri->hostname) : NULL; + this->http_port = uri->port; + this->proxy_user = (proxy_uri->user) ? strdup(proxy_uri->user) : NULL; + this->proxy_password = (proxy_uri->passwd) ? strdup(proxy_uri->passwd) : NULL; + this->host_user = (uri->user) ? strdup(uri->user) : NULL; + this->host_password = (uri->passwd) ? strdup(uri->passwd) : NULL; + gnet_uri_set_scheme(uri,"http"); + this->uri = gnet_mms_helper(uri, 1); + } else { + this->proto = (uri->scheme) ? strdup(uri->scheme) : NULL; + this->connect_host = (uri->hostname) ? strdup(uri->hostname) : NULL; + this->connect_port = uri->port; + this->http_host = (uri->hostname) ? strdup(uri->hostname) : NULL; + this->http_port = uri->port; + this->proxy_user = NULL; + this->proxy_password = NULL; + this->host_user =(uri->user) ? strdup(uri->user) : NULL; + this->host_password = (uri->passwd) ? strdup(uri->passwd) : NULL; + this->uri = gnet_mms_helper(uri, 1); + } + + if(!this->uri) + goto fail; + + if (proxy_uri) { + gnet_uri_delete(proxy_uri); + proxy_uri = NULL; + } + if (uri) { + gnet_uri_delete(uri); + uri = NULL; + } + if (!mmsh_valid_proto(this->proto)) { + lprintf("mmsh: unsupported protocol\n"); + goto fail; + } + + if (!mmsh_connect_int(io, this, 0, 0)) + goto fail; + + return this; + +fail: + lprintf("mmsh: connect failed\n"); + if (proxy_uri) + gnet_uri_delete(proxy_uri); + if (uri) + gnet_uri_delete(uri); + if (this->s != -1) + close(this->s); + if (this->url) + free(this->url); + if (this->proxy_url) + free(this->proxy_url); + if (this->proto) + free(this->proto); + if (this->connect_host) + free(this->connect_host); + if (this->http_host) + free(this->http_host); + if (this->proxy_user) + free(this->proxy_user); + if (this->proxy_password) + free(this->proxy_password); + if (this->host_user) + free(this->host_user); + if (this->host_password) + free(this->host_password); + if (this->uri) + free(this->uri); + + free(this); + return NULL; +} + +static int get_media_packet (mms_io_t *io, mmsh_t *this) { + int ret, len = 0; + + if (get_chunk_header(io, this) == SUCCESS) { + switch (this->chunk_type) { + case CHUNK_TYPE_END: + /* this->chunk_seq_number: + * 0: stop + * 1: a new stream follows + */ + if (this->chunk_seq_number == 0) + return EOS; + + this->http_request_number = 1; + if (!mmsh_connect_int (io, this, 0, 0)) + return ERROR; + + /* What todo with: current_pos ?? + Also our chunk_seq_numbers will probably restart from 0! + If this happens with a seekable stream (does it ever?) + and we get a seek request after this were fscked! */ + this->seekable = 0; + + /* mmsh_connect_int reads the first data packet */ + /* this->buf_size is set by mmsh_connect_int */ + return GOT_HEADER_N_DATA; + + case CHUNK_TYPE_DATA: + /* nothing to do */ + break; + + case CHUNK_TYPE_RESET: + /* next chunk is an ASF header */ + + if (this->chunk_length != 0) { + /* that's strange, don't know what to do */ + lprintf("mmsh: non 0 sized reset chunk"); + return ERROR; + } + if ((ret = get_header (io, this)) != SUCCESS) { + lprintf("mmsh: failed to get header after reset chunk\n"); + return ret; + } + interp_header(io, this); + + /* What todo with: current_pos ?? + Also our chunk_seq_numbers might restart from 0! + If this happens with a seekable stream (does it ever?) + and we get a seek request after this were fscked! */ + this->seekable = 0; + + /* get_header reads the first data packet */ + /* this->buf_size is set by get_header */ + return GOT_HEADER_N_DATA; + + default: + lprintf("mmsh: unexpected chunk_type(0x%04x)\n", this->chunk_type); + return ERROR; + } + + len = io_read (io, this->s, this->buf, this->chunk_length); + + if (len == this->chunk_length) { + /* explicit padding with 0 */ + if (this->chunk_length > this->packet_length) { + lprintf("mmsh: chunk_length(%d) > packet_length(%d)\n", + this->chunk_length, this->packet_length); + return ERROR; + } + + memset(this->buf + this->chunk_length, 0, + this->packet_length - this->chunk_length); + this->buf_size = this->packet_length; + + return SUCCESS; + } else { + lprintf("mmsh: media packet read error, %d != %d\n", len, + this->chunk_length); + return ERROR; + } + } else if (ret == EOS) { + return EOS; + } else { + lprintf("mmsh: get_media_packet failed to get chunk header\n"); + return ret; + } +} + +int mmsh_peek_header (mmsh_t *this, char *data, int maxsize) { + int len; + + len = (this->asf_header_len < maxsize) ? this->asf_header_len : maxsize; + + memcpy(data, this->asf_header, len); + return len; +} + +int mmsh_read (mms_io_t *io, mmsh_t *this, char *data, int len) { + int total; + + total = 0; + + /* Check if the stream didn't get closed because of previous errors */ + if (this->s == -1) + return total; + + while (total < len) { + + if (this->asf_header_read < this->asf_header_len) { + int n, bytes_left ; + + bytes_left = this->asf_header_len - this->asf_header_read ; + + if ((len-total) < bytes_left) + n = len-total; + else + n = bytes_left; + + memcpy (&data[total], &this->asf_header[this->asf_header_read], n); + + this->asf_header_read += n; + total += n; + this->current_pos += n; + } else { + + int n, bytes_left ; + + bytes_left = this->buf_size - this->buf_read; + + if (bytes_left == 0) { + int ret; + + this->buf_size=this ->buf_read = 0; + ret = get_media_packet (io, this); + + switch (ret) { + case SUCCESS: + break; + case ERROR: + lprintf ("mmsh: get_media_packet failed\n"); + return total; + case EOS: + return total; + case GOT_HEADER_N_DATA: + continue; + } + bytes_left = this->buf_size; + } + + if ((len-total) < bytes_left) + n = len-total; + else + n = bytes_left; + + memcpy (&data[total], &this->buf[this->buf_read], n); + + this->buf_read += n; + total += n; + this->current_pos += n; + } + } + return total; +} + +off_t mmsh_seek (mms_io_t *io, mmsh_t *this, off_t offset, int origin) { + off_t dest; + off_t dest_packet_seq; + uint32_t orig_asf_header_len = this->asf_header_len; + uint32_t orig_asf_packet_len = this->packet_length; + + if (!this->seekable) + return this->current_pos; + + switch (origin) { + case SEEK_SET: + dest = offset; + break; + case SEEK_CUR: + dest = this->current_pos + offset; + break; + case SEEK_END: + dest = mmsh_get_length (this) + offset; + default: + return this->current_pos; + } + + dest_packet_seq = dest - this->asf_header_len; + dest_packet_seq = dest_packet_seq >= 0 ? + dest_packet_seq / this->packet_length : -1; + + if (dest_packet_seq < 0) { + if (this->chunk_seq_number > 0) { + lprintf("mmsh: seek within header, already read beyond first packet, resetting connection\n"); + if (!mmsh_connect_int(io, this, 0, 0)) { + /* Oops no more connection let our caller know things are fscked up */ + return this->current_pos = -1; + } + /* Some what simple / naive check to check for changed headers + if the header was changed we are once more fscked up */ + if (this->asf_header_len != orig_asf_header_len || + this->packet_length != orig_asf_packet_len) { + lprintf("mmsh: AIIEEE asf header or packet length changed on re-open for seek\n"); + /* Its a different stream, so its useless! */ + close (this->s); + this->s = -1; + return this->current_pos = -1; + } + } else + lprintf("mmsh: seek within header, resetting buf_read\n"); + + // reset buf_read + this->buf_read = 0; + this->asf_header_read = dest; + return this->current_pos = dest; + } + + // dest_packet_seq >= 0 + if (this->asf_num_packets && dest == this->asf_header_len + + this->asf_num_packets*this->packet_length) { + // Requesting the packet beyond the last packet, can cause the server to + // not return any packet or any eos command. This can cause + // mms_packet_seek() to hang. + // This is to allow seeking at end of stream, and avoid hanging. + --dest_packet_seq; + lprintf("mmsh: seek to eos!\n"); + } + + if (dest_packet_seq != this->chunk_seq_number) { + + if (this->asf_num_packets && dest_packet_seq >= this->asf_num_packets) { + // Do not seek beyond the last packet. + return this->current_pos; + } + + lprintf("mmsh: seek to %d, packet: %d\n", (int)dest, (int)dest_packet_seq); + if (!mmsh_connect_int(io, this, (dest_packet_seq+1) * this->packet_length, 0)) { + /* Oops no more connection let our caller know things are fscked up */ + return this->current_pos = -1; + } + /* Some what simple / naive check to check for changed headers + if the header was changed we are once more fscked up */ + if (this->asf_header_len != orig_asf_header_len || + this->packet_length != orig_asf_packet_len) { + lprintf("mmsh: AIIEEE asf header or packet length changed on re-open for seek\n"); + /* Its a different stream, so its useless! */ + close (this->s); + this->s = -1; + return this->current_pos = -1; + } + } + else + lprintf("mmsh: seek within current packet, dest: %d, current pos: %d\n", + (int)dest, (int)this->current_pos); + + /* make sure asf_header is seen as fully read by mmsh_read() this is needed + in case our caller tries to seek over part of the header, or when we've + done an actual packet seek as get_header() resets asf_header_read then. */ + this->asf_header_read = this->asf_header_len; + + /* check we got what we want */ + if (dest_packet_seq == this->chunk_seq_number) { + this->buf_read = dest - + (this->asf_header_len + dest_packet_seq*this->packet_length); + this->current_pos = dest; + } else { + lprintf("mmsh: Seek failed, wanted packet: %d, got packet: %d\n", + (int)dest_packet_seq, (int)this->chunk_seq_number); + this->buf_read = 0; + this->current_pos = this->asf_header_len + this->chunk_seq_number * + this->packet_length; + } + + lprintf("mmsh: current_pos after seek to %d: %d (buf_read %d)\n", + (int)dest, (int)this->current_pos, (int)this->buf_read); + + return this->current_pos; +} + +int mmsh_time_seek (mms_io_t *io, mmsh_t *this, double time_sec) { + uint32_t orig_asf_header_len = this->asf_header_len; + uint32_t orig_asf_packet_len = this->packet_length; + + if (!this->seekable) + return 0; + + lprintf("mmsh: time seek to %f secs\n", time_sec); + if (!mmsh_connect_int(io, this, 0, time_sec * 1000 + this->preroll)) { + /* Oops no more connection let our caller know things are fscked up */ + this->current_pos = -1; + return 0; + } + /* Some what simple / naive check to check for changed headers + if the header was changed we are once more fscked up */ + if (this->asf_header_len != orig_asf_header_len || + this->packet_length != orig_asf_packet_len) { + lprintf("mmsh: AIIEEE asf header or packet length changed on re-open for seek\n"); + /* Its a different stream, so its useless! */ + close (this->s); + this->s = -1; + this->current_pos = -1; + return 0; + } + + this->asf_header_read = this->asf_header_len; + this->buf_read = 0; + this->current_pos = this->asf_header_len + this->chunk_seq_number * + this->packet_length; + + lprintf("mmsh, current_pos after time_seek:%d\n", (int)this->current_pos); + + return 1; +} + +void mmsh_close (mmsh_t *this) { + if (this->s != -1) + close(this->s); + if (this->url) + free(this->url); + if (this->proxy_url) + free(this->proxy_url); + if (this->proto) + free(this->proto); + if (this->connect_host) + free(this->connect_host); + if (this->http_host) + free(this->http_host); + if (this->proxy_user) + free(this->proxy_user); + if (this->proxy_password) + free(this->proxy_password); + if (this->host_user) + free(this->host_user); + if (this->host_password) + free(this->host_password); + if (this->uri) + free(this->uri); + if (this) + free (this); +} + + +uint32_t mmsh_get_length (mmsh_t *this) { + /* we could / should return this->file_len here, but usually this->file_len + is longer then the calculation below, as usually an asf file contains an + asf index object after the data stream. However since we do not have a + (known) way to get to this index object through mms, we return a + calculated size of what we can get to when we know. */ + if (this->asf_num_packets) + return this->asf_header_len + this->asf_num_packets*this->packet_length; + else + return this->file_length; +} + +double mmsh_get_time_length (mmsh_t *this) { + return (double)(this->time_len) / 1e7; +} + +uint64_t mmsh_get_raw_time_length (mmsh_t *this) { + return this->time_len; +} + +off_t mmsh_get_current_pos (mmsh_t *this) { + return this->current_pos; +} + +uint32_t mmsh_get_asf_header_len (mmsh_t *this) { + return this->asf_header_len; +} + +uint32_t mmsh_get_asf_packet_len (mmsh_t *this) { + return this->packet_length; +} + +int mmsh_get_seekable (mmsh_t *this) { + return this->seekable; +} diff --git a/plugins/mms/libmms/mmsh.h b/plugins/mms/libmms/mmsh.h new file mode 100644 index 00000000..b222eeaa --- /dev/null +++ b/plugins/mms/libmms/mmsh.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2002-2003 the xine project + * + * This file is part of xine, a free video player. + * + * xine is free software; you can 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. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 + * + * $Id: mmsh.h,v 1.8 2007/12/11 20:24:48 jwrdegoede Exp $ + * + * libmmsh public header + */ + +#ifndef HAVE_MMSH_H +#define HAVE_MMSH_H + +#include <inttypes.h> +#include <stdio.h> +#include <sys/types.h> +#include "mmsio.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct mmsh_s mmsh_t; + +char* mmsh_connect_common(int *s ,int *port, char *url, char **host, char **path, char **file); +mmsh_t* mmsh_connect (mms_io_t *io, void *data, const char *url_, int bandwidth); + +int mmsh_read (mms_io_t *io, mmsh_t *instance, char *data, int len); +int mmsh_time_seek (mms_io_t *io, mmsh_t *instance, double time_sec); +mms_off_t mmsh_seek (mms_io_t *io, mmsh_t *instance, mms_off_t offset, int origin); +uint32_t mmsh_get_length (mmsh_t *instance); +double mmsh_get_time_length (mmsh_t *instance); +uint64_t mmsh_get_raw_time_length (mmsh_t *instance); +mms_off_t mmsh_get_current_pos (mmsh_t *instance); +void mmsh_close (mmsh_t *instance); + +int mmsh_peek_header (mmsh_t *instance, char *data, int maxsize); + +uint32_t mmsh_get_asf_header_len (mmsh_t *instance); + +uint32_t mmsh_get_asf_packet_len (mmsh_t *instance); + +int mmsh_get_seekable (mmsh_t *instance); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/plugins/mms/libmms/mmsio.h b/plugins/mms/libmms/mmsio.h new file mode 100644 index 00000000..8e4304d9 --- /dev/null +++ b/plugins/mms/libmms/mmsio.h @@ -0,0 +1,93 @@ +#ifndef __MMS_IO_H__ +#define __MMS_IO_H__ + +#define LIBMMS_HAVE_64BIT_OFF_T 1 + +/* On 64 bit file offset capable systems, libmms' configure script adds + -D_FILE_OFFSET_BITS=64 to the CFLAGS. This causes off_t to be 64 bit, + When an app which includes this header file gets compiled without + -D_FILE_OFFSET_BITS=64, it should still expect / pass 64 bit ints for + off_t, this acomplishes this: */ +#if defined LIBMMS_HAVE_64BIT_OFF_T && !defined __MMS_C__ +#define mms_off_t int64_t +#else +#define mms_off_t off_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef mms_off_t (*mms_io_write_func)(void *data, int socket, char *buf, mms_off_t num); +typedef mms_off_t (*mms_io_read_func)(void *data, int socket, char *buf, mms_off_t num); + +/* select states */ +#define MMS_IO_READ_READY 1 +#define MMS_IO_WRITE_READY 2 + +enum + { + MMS_IO_STATUS_READY, /* IO can be safely performed */ + MMS_IO_STATUS_ERROR, /* There was IO error */ + MMS_IO_STATUS_ABORTED, /* IO command was (somehow) + aborted. This is not error, but invalidates IO for further operations*/ + MMS_IO_STATUS_TIMEOUT /* Timeout was exceeded */ + }; + +/* + * Waits for a file descriptor/socket to change status. + * + * users can use this handler to provide their own implementations, + * for example abortable ones + * + * params : + * data whatever parameter may be needed by implementation + * fd file/socket descriptor + * state MMS_IO_READ_READY, MMS_IO_WRITE_READY + * timeout_sec timeout in seconds + * + * + * return value : + * MMS_IO_READY the file descriptor is ready for cmd + * MMS_IO_ERROR an i/o error occured + * MMS_IO_ABORTED command aborted + * MMS_IO_TIMEOUT the file descriptor is not ready after timeout_msec milliseconds + * every other return value is interpreted same as MMS_IO_ABORTED + */ +typedef int (*mms_io_select_func)(void *data, int fd, int state, int timeout_msec); + +/* + * open a tcp connection + * + * params : + * stream needed for reporting errors but may be NULL + * host address of target + * port port on target + * + * returns a socket descriptor or -1 if an error occured + */ +typedef int (*mms_io_tcp_connect_func)(void *data, const char *host, int port); + +typedef struct +{ + mms_io_select_func select; + void *select_data; + mms_io_read_func read; + void *read_data; + mms_io_write_func write; + void *write_data; + mms_io_tcp_connect_func connect; + void *connect_data; +} mms_io_t; + +/* set default IO implementation, it will be used in absence of specific IO + parameter. Structure is referenced, not copied, must remain valid for entire + usage period. Passing NULL reverts to default, POSIX based implementation */ +void mms_set_default_io_impl(const mms_io_t *io); +const mms_io_t* mms_get_default_io_impl(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __MMS_IO_H__ */ diff --git a/plugins/mms/libmms/mmsx.c b/plugins/mms/libmms/mmsx.c new file mode 100644 index 00000000..fc95de86 --- /dev/null +++ b/plugins/mms/libmms/mmsx.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl> + * + * This file is part of libmms a free mms protocol library + * + * libmms is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libmss is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + */ + +/* + * mmsx is a small wrapper around the mms and mmsh protocol implementations + * in libmms. The mmsx functions provide transparent access to both protocols + * so that programs who wish to support both can do so with a single code path + * if desired. + */ + +#include <stdlib.h> +#include "mmsx.h" +#include "mms.h" +#include "mmsh.h" + +struct mmsx_s { + mms_t *connection; + mmsh_t *connection_h; +}; + +mmsx_t *mmsx_connect(mms_io_t *io, void *data, const char *url, int bandwidth) +{ + mmsx_t *mmsx = calloc(1, sizeof(mmsx_t)); + char *try_mms_first = getenv("LIBMMS_TRY_MMS_FIRST"); + + if (!mmsx) + return mmsx; + + /* Normally we try mmsh first, as mms: is a rollover protocol identifier + according to microsoft and recent mediaplayer versions will try + mmsh before mms for mms:// uris. Note that in case of a mmst:// or a + mmsh:// url the mms[h]_connect function will directly exit if it cannot + handle it. The LIBMMS_TRY_MMS_FIRST environment variable is there for + testing the mms code against servers which accept both mmsh and mms. */ + if (try_mms_first, 1) { + mmsx->connection = mms_connect(io, data, url, bandwidth); + if (mmsx->connection) + return mmsx; + } + + mmsx->connection_h = mmsh_connect(io, data, url, bandwidth); + if (mmsx->connection_h) + return mmsx; + + if (!try_mms_first, 0) { + mmsx->connection = mms_connect(io, data, url, bandwidth); + if (mmsx->connection) + return mmsx; + } + + free(mmsx); + return NULL; +} + +int mmsx_read (mms_io_t *io, mmsx_t *mmsx, char *data, int len) +{ + if(mmsx->connection) + return mms_read(io, mmsx->connection, data, len); + else + return mmsh_read(io, mmsx->connection_h, data, len); +} + +int mmsx_time_seek (mms_io_t *io, mmsx_t *mmsx, double time_sec) +{ + if(mmsx->connection) + return mms_time_seek(io, mmsx->connection, time_sec); + else + return mmsh_time_seek(io, mmsx->connection_h, time_sec); +} + +mms_off_t mmsx_seek (mms_io_t *io, mmsx_t *mmsx, mms_off_t offset, int origin) +{ + if(mmsx->connection) + return mms_seek(io, mmsx->connection, offset, origin); + else + return mmsh_seek(io, mmsx->connection_h, offset, origin); +} + +double mmsx_get_time_length (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_time_length(mmsx->connection); + else + return mmsh_get_time_length(mmsx->connection_h); +} + +uint64_t mmsx_get_raw_time_length (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_raw_time_length(mmsx->connection); + else + return mmsh_get_raw_time_length(mmsx->connection_h); +} + +uint32_t mmsx_get_length (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_length(mmsx->connection); + else + return mmsh_get_length(mmsx->connection_h); +} + +void mmsx_close (mmsx_t *mmsx) +{ + if(mmsx->connection) + mms_close(mmsx->connection); + else + mmsh_close(mmsx->connection_h); + + free(mmsx); +} + +int mmsx_peek_header (mmsx_t *mmsx, char *data, int maxsize) +{ + if(mmsx->connection) + return mms_peek_header(mmsx->connection, data, maxsize); + else + return mmsh_peek_header(mmsx->connection_h, data, maxsize); +} + +mms_off_t mmsx_get_current_pos (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_current_pos(mmsx->connection); + else + return mmsh_get_current_pos(mmsx->connection_h); +} + +uint32_t mmsx_get_asf_header_len(mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_asf_header_len(mmsx->connection); + else + return mmsh_get_asf_header_len(mmsx->connection_h); +} + +uint64_t mmsx_get_asf_packet_len (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_asf_packet_len(mmsx->connection); + else + return mmsh_get_asf_packet_len(mmsx->connection_h); +} + +int mmsx_get_seekable (mmsx_t *mmsx) +{ + if(mmsx->connection) + return mms_get_seekable(mmsx->connection); + else + return mmsh_get_seekable(mmsx->connection_h); +} diff --git a/plugins/mms/libmms/mmsx.h b/plugins/mms/libmms/mmsx.h new file mode 100644 index 00000000..df4adfce --- /dev/null +++ b/plugins/mms/libmms/mmsx.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl> + * + * This file is part of libmms a free mms protocol library + * + * libmms is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * libmss is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * + * libmms public header + */ + +/* + * mmsx is a small wrapper around the mms and mmsh protocol implementations + * in libmms. The mmsx functions provide transparent access to both protocols + * so that programs who wish to support both can do so with a single code path + * if desired. + */ + +#ifndef HAVE_MMSX_H +#define HAVE_MMSX_H + +#include <inttypes.h> +#include "mmsio.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +typedef struct mmsx_s mmsx_t; + +mmsx_t* mmsx_connect (mms_io_t *io, void *data, const char *url, int bandwidth); + +int mmsx_read (mms_io_t *io, mmsx_t *instance, char *data, int len); +int mmsx_time_seek (mms_io_t *io, mmsx_t *instance, double time_sec); +mms_off_t mmsx_seek (mms_io_t *io, mmsx_t *instance, mms_off_t offset, int origin); +/* return total playback time in seconds */ +double mmsx_get_time_length (mmsx_t *instance); +/* return raw total playback time in 100 nanosecs (10^-7) */ +uint64_t mmsx_get_raw_time_length (mmsx_t *instance); +uint32_t mmsx_get_length (mmsx_t *instance); +void mmsx_close (mmsx_t *instance); + +int mmsx_peek_header (mmsx_t *instance, char *data, int maxsize); + +mms_off_t mmsx_get_current_pos (mmsx_t *instance); + +uint32_t mmsx_get_asf_header_len (mmsx_t *instance); + +uint64_t mmsx_get_asf_packet_len (mmsx_t *instance); + +int mmsx_get_seekable (mmsx_t *instance); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif + diff --git a/plugins/mms/libmms/uri.c b/plugins/mms/libmms/uri.c new file mode 100644 index 00000000..6b6475eb --- /dev/null +++ b/plugins/mms/libmms/uri.c @@ -0,0 +1,1033 @@ +/* GNet - Networking library + * Copyright (C) 2000-2003 David Helder, David Bolcsfoldi, Eric Williams + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* FIXME: #include "gnet-private.h" */ +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdio.h> + +#include "uri.h" +#include <string.h> + +static void field_unescape (char *str); +static char* field_escape (char* str, unsigned char mask); + +#define USERINFO_ESCAPE_MASK 0x01 +#define PATH_ESCAPE_MASK 0x02 +#define QUERY_ESCAPE_MASK 0x04 +#define FRAGMENT_ESCAPE_MASK 0x08 + +/* #define FALSE 0 */ +/* #define TRUE (!FALSE) */ + +static unsigned char neednt_escape_table[] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0e, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x0f, 0x00, 0x0c, + 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x00, 0x0f, 0x00, 0x00, 0x0f, + 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + + +/* +Perl code to generate above table: + +#!/usr/bin/perl + +$ok = "abcdefghijklmnopqrstuvwxyz" . + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" . + "0123456789" . + "-_.!~*'()"; +$userinfo_ok = ';:&=+\$,'; +$path_ok = ':\@&=+\$,;/'; +$query_ok = ';/?:\@&=+\$,'; +$fragment_ok = ';/?:\@&=+\$,'; + +for ($i = 0; $i < 32; $i++) +{ + print " "; + for ($j = 0; $j < 8; $j++) + { + $num = 0; + $letter = chr(($i * 8) + $j); + + $num |= 0b0001 if (index($userinfo_ok, $letter) != -1); + $num |= 0b0010 if (index($path_ok, $letter) != -1); + $num |= 0b0100 if (index($query_ok, $letter) != -1); + $num |= 0b1000 if (index($fragment_ok, $letter) != -1); + $num |= 0b1111 if (index($ok, $letter) != -1); + + printf "0x%02x, ", $num; + } + print "\n"; +} +*/ + + +/* our own ISSPACE. ANSI isspace is local dependent */ +#define ISSPACE(C) (((C) >= 9 && (C) <= 13) || (C) == ' ') + + +static int split_user_passwd(const char* in, char** user, char** passwd) +{ + char *tmp = strdup(in); + + if(!tmp) + return 0; + *passwd = strchr(tmp, ':'); + if(!(*passwd)) + { + free(tmp); + return 0; + } + *((*passwd)++) = '\0'; // don't you love C? :) + + *user = strdup(tmp); + if(!*user) + return 0; + *passwd = strdup(*passwd); + if(!*passwd) + return 0; + + free(tmp); + return 1; +} + +/** + * gnet_uri_new + * @uri: URI string + * + * Creates a #GURI from a string. Empty fields are set to NULL. The + * parser does not validate the URI -- it will accept some malformed + * URI. URIs are usually in the form + * scheme://userinfo@hostname:port/path?query#fragment + * + * URIs created from user input are typically unescaped. URIs + * created from machine input (e.g. received over the internet) are + * typically escaped. + * + * Returns: a new #GURI, or NULL if there was a failure. + * + **/ +GURI* +gnet_uri_new (const char* uri) +{ + GURI* guri = NULL; + const char* p; + const char* temp; + + if (!uri) { + return NULL; + } + + /* Skip initial whitespace */ + p = uri; + while (*p && ISSPACE((int)*p)) + ++p; + if (!*p) /* Error if it's just a string of space */ + return NULL; + + guri = malloc (sizeof (GURI)); + memset (guri, 0, sizeof (GURI)); + + /* Scheme */ + temp = p; + while (*p && *p != ':' && *p != '/' && *p != '?' && *p != '#') + ++p; + if (*p == ':') + { + guri->scheme = strndup (temp, p - temp); + ++p; + } + else /* This char is NUL, /, ?, or # */ + p = temp; + + /* Authority */ + if (*p == '/' && p[1] == '/') + { + char *userinfo; + p += 2; + + /* Userinfo */ + temp = p; + while (*p && *p != '@' && *p != '/' ) /* Look for @ or / */ + ++p; + if (*p == '@') /* Found userinfo */ + { + userinfo = strndup (temp, p - temp); + if(!split_user_passwd(userinfo, &guri->user, &guri->passwd)) + { + free(userinfo); + goto error; + } + free(userinfo); + ++p; + } + else + p = temp; + + /* Hostname */ + + /* Check for IPv6 canonical hostname in brackets */ + if (*p == '[') + { + p++; /* Skip [ */ + temp = p; + while (*p && *p != ']') ++p; + if ((p - temp) == 0) + goto error; + guri->hostname = strndup (temp, p - temp); + if (*p) + p++; /* Skip ] (if there) */ + } + else + { + temp = p; + while (*p && *p != '/' && *p != '?' && *p != '#' && *p != ':') ++p; + if ((p - temp) == 0) + goto error; + guri->hostname = strndup (temp, p - temp); + } + + /* Port */ + if (*p == ':') + { + for (++p; isdigit((int)*p); ++p) + guri->port = guri->port * 10 + (*p - '0'); + } + + } + + /* Path (we are liberal and won't check if it starts with /) */ + temp = p; + while (*p && *p != '?' && *p != '#') + ++p; + if (p != temp) + guri->path = strndup(temp, p - temp); + + /* Query */ + if (*p == '?') + { + temp = p + 1; + while (*p && *p != '#') + ++p; + guri->query = strndup (temp, p - temp); + } + + /* Fragment */ + if (*p == '#') + { + ++p; + guri->fragment = strdup (p); + } + + return guri; + + error: + gnet_uri_delete (guri); + return NULL; +} + + +/** + * gnet_uri_new_fields + * @scheme: scheme + * @hostname: host name + * @port: port + * @path: path + * + * Creates a #GURI from the fields. This function uses the most + * common fields. Use gnet_uri_new_fields_all() to specify all + * fields. + * + * Returns: a new #GURI. + * + **/ +GURI* +gnet_uri_new_fields (const char* scheme, const char* hostname, + const int port, const char* path) +{ + GURI* uri = NULL; + + uri = malloc (sizeof (GURI)); + memset (uri, 0, sizeof (GURI)); + if (scheme) uri->scheme = strdup (scheme); + if (hostname) uri->hostname = strdup (hostname); + uri->port = port; + if (path) uri->path = strdup (path); + + return uri; +} + + +/** + * gnet_uri_new_fields_all + * @scheme: scheme + * @userinfo: user info + * @hostname: host name + * @port: port + * @path: path + * @query: query + * @fragment: fragment + * + * Creates a #GURI from all fields. + * + * Returns: a new #GURI. + * + **/ +GURI* +gnet_uri_new_fields_all (const char* scheme, const char* user, + const char* passwd, const char* hostname, + const int port, const char* path, + const char* query, const char* fragment) +{ + GURI* uri = NULL; + + uri = malloc (sizeof (GURI)); + memset (uri, 0, sizeof (GURI)); + if (scheme) uri->scheme = strdup (scheme); + if (user) uri->user = strdup (user); + if (passwd) uri->passwd = strdup (passwd); + if (hostname) uri->hostname = strdup (hostname); + uri->port = port; + if (path) uri->path = strdup (path); + if (query) uri->query = strdup (query); + if (fragment) uri->fragment = strdup (fragment); + + return uri; +} + + +/** + * gnet_uri_clone: + * @uri: a #GURI + * + * Copies a #GURI. + * + * Returns: a copy of @uri. + * + **/ +GURI* +gnet_uri_clone (const GURI* uri) +{ + GURI* uri2; + + if (!uri) { + return NULL; + } + + uri2 = malloc (sizeof (GURI)); + memset (uri2, 0, sizeof (GURI)); + uri2->scheme = strdup (uri->scheme); + uri2->user = strdup (uri->user); + uri2->passwd = strdup (uri->passwd); + uri2->hostname = strdup (uri->hostname); + uri2->port = uri->port; + uri2->path = strdup (uri->path); + uri2->query = strdup (uri->query); + uri2->fragment = strdup (uri->fragment); + + return uri2; +} + + +/** + * gnet_uri_delete: + * @uri: a #GURI + * + * Deletes a #GURI. + * + **/ +void +gnet_uri_delete (GURI* uri) +{ + if (uri) + { + free (uri->scheme); + free (uri->user); + free (uri->passwd); + free (uri->hostname); + free (uri->path); + free (uri->query); + free (uri->fragment); + free (uri); + } +} + + + + +#define SAFESTRCMP(A,B) (((A)&&(B))?(strcmp((A),(B))):((A)||(B))) + +/** + * gnet_uri_equal + * @p1: a #GURI + * @p2: another #GURI + * + * Compares two #GURI's for equality. + * + * Returns: TRUE if they are equal; FALSE otherwise. + * + **/ +int +gnet_uri_equal (const char * p1, const char * p2) +{ + const GURI* uri1 = (const GURI*) p1; + const GURI* uri2 = (const GURI*) p2; + + if (!uri1) { + return 0; + } + if (!uri2) { + return 0; + } + + if (uri1->port == uri2->port && + !SAFESTRCMP(uri1->scheme, uri2->scheme) && + !SAFESTRCMP(uri1->user, uri2->user) && + !SAFESTRCMP(uri1->passwd, uri2->passwd) && + !SAFESTRCMP(uri1->hostname, uri2->hostname) && + !SAFESTRCMP(uri1->path, uri2->path) && + !SAFESTRCMP(uri1->query, uri2->query) && + !SAFESTRCMP(uri1->fragment, uri2->fragment)) + return 1; + + return 0; +} + + +/** + * gnet_uri_hash + * @p: a #GURI + * + * Creates a hash code for @p for use with GHashTable. + * + * Returns: hash code for @p. + * + **/ +#if 0 +unsigned int +gnet_uri_hash (const char * p) +{ + const GURI* uri = (const GURI*) p; + unsigned int h = 0; + + if (!uri) { + return 0; + } + + if (uri->scheme) h = g_str_hash (uri->scheme); + if (uri->user) h ^= g_str_hash (uri->user); + if (uri->passwd) h ^= g_str_hash (uri->passwd); + if (uri->hostname) h ^= g_str_hash (uri->hostname); + h ^= uri->port; + if (uri->path) h ^= g_str_hash (uri->path); + if (uri->query) h ^= g_str_hash (uri->query); + if (uri->fragment) h ^= g_str_hash (uri->fragment); + + return h; +} +#endif + + +/** + * gnet_uri_escape + * @uri: a #GURI + * + * Escapes the fields in a #GURI. Network protocols use escaped + * URIs. People use unescaped URIs. + * + **/ +void +gnet_uri_escape (GURI* uri) +{ + if (!uri) { + return; + } + + uri->user = field_escape (uri->user, USERINFO_ESCAPE_MASK); + uri->passwd = field_escape (uri->passwd, USERINFO_ESCAPE_MASK); + uri->path = field_escape (uri->path, PATH_ESCAPE_MASK); + uri->query = field_escape (uri->query, QUERY_ESCAPE_MASK); + uri->fragment = field_escape (uri->fragment, FRAGMENT_ESCAPE_MASK); +} + + +/** + * gnet_uri_unescape + * @uri: a #GURI + * + * Unescapes the fields in the URI. Network protocols use escaped + * URIs. People use unescaped URIs. + * + **/ +void +gnet_uri_unescape (GURI* uri) +{ + if (!uri) { + return; + } + + if (uri->user) + field_unescape (uri->user); + if (uri->passwd) + field_unescape (uri->passwd); + if (uri->path) + field_unescape (uri->path); + if (uri->query) + field_unescape (uri->query); + if (uri->fragment) + field_unescape (uri->fragment); +} + + +static char* +field_escape (char* str, unsigned char mask) +{ + int len; + int i; + int must_escape = 0; + char* dst; + int j; + + if (str == NULL) + return NULL; + + /* Roughly calculate buffer size */ + len = 0; + for (i = 0; str[i]; i++) + { + if (neednt_escape_table[(unsigned int) str[i]] & mask) + len++; + else + { + len += 3; + must_escape = 1; + } + } + + /* Don't escape if unnecessary */ + if (must_escape == 0) + return str; + + /* Allocate buffer */ + dst = (char*) malloc(len + 1); + memset (dst, 0, len+1); + + /* Copy */ + for (i = j = 0; str[i]; i++, j++) + { + /* Unescaped character */ + if (neednt_escape_table[(unsigned int) str[i]] & mask) + { + dst[j] = str[i]; + } + + /* Escaped character */ + else + { + dst[j] = '%'; + + if (((str[i] & 0xf0) >> 4) < 10) + dst[j+1] = ((str[i] & 0xf0) >> 4) + '0'; + else + dst[j+1] = ((str[i] & 0xf0) >> 4) + 'a' - 10; + + if ((str[i] & 0x0f) < 10) + dst[j+2] = (str[i] & 0x0f) + '0'; + else + dst[j+2] = (str[i] & 0x0f) + 'a' - 10; + + j += 2; /* and j is incremented in loop too */ + } + } + dst[j] = '\0'; + + free (str); + return dst; +} + + + +static void +field_unescape (char* s) +{ + char* src; + char* dst; + + for (src = dst = s; *src; ++src, ++dst) + { + if (src[0] == '%' && src[1] != '\0' && src[2] != '\0') + { + int high, low; + + if ('a' <= src[1] && src[1] <= 'f') + high = src[1] - 'a' + 10; + else if ('A' <= src[1] && src[1] <= 'F') + high = src[1] - 'A' + 10; + else if ('0' <= src[1] && src[1] <= '9') + high = src[1] - '0'; + else /* malformed */ + goto regular_copy; + + if ('a' <= src[2] && src[2] <= 'f') + low = src[2] - 'a' + 10; + else if ('A' <= src[2] && src[2] <= 'F') + low = src[2] - 'A' + 10; + else if ('0' <= src[2] && src[2] <= '9') + low = src[2] - '0'; + else /* malformed */ + goto regular_copy; + + *dst = (char)((high << 4) + low); + src += 2; + } + else + { + regular_copy: + *dst = *src; + } + } + + *dst = '\0'; +} + + + +/** + * gnet_uri_get_string + * @uri: a #GURI + * + * Gets a string representation of a #GURI. This function does not + * escape or unescape the fields first. Call gnet_uri_escape() or + * gnet_uri_unescape first if necessary. + * + * Returns: a string. + * + **/ +char* +gnet_uri_get_string (const GURI* uri) +{ + char* rv = NULL; + char *buffer = malloc (1024); + memset (buffer, 0, 1024); + char *b = buffer; + int remaining = 1024; + + if (!uri) { + return NULL; + } + + if (uri->scheme) { + int n = snprintf (buffer, n, "%s:", uri->scheme); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + if (uri->user || uri->passwd || uri->hostname || uri->port) { + strcpy (buffer, "//"); + buffer += 2; + remaining -= 2; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + if (uri->user) + { + int n = strlen (uri->user); + memcpy (buffer, uri->user, n+1); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + strcpy (buffer, "@"); + buffer += 1; + remaining -= 1; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + if (uri->passwd) + { + int n = strlen (uri->passwd); + memcpy (buffer, uri->passwd, n+1); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + strcpy (buffer, "@"); + buffer += 1; + remaining -= 1; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + /* Add brackets around the hostname if it's IPv6 */ + if (uri->hostname) + { + if (strchr(uri->hostname, ':') == NULL) { + int n = strlen (uri->hostname); + memcpy (buffer, uri->hostname, n+1); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + else { + int n = snprintf (buffer, remaining, "[%s]", uri->hostname); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + } + + if (uri->port) { + int n = snprintf (buffer, remaining, ":%d", uri->port); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + if (uri->path) + { + if (*uri->path == '/' || + !(uri->user || uri->passwd || uri->hostname || uri->port)) { + int n = strlen (uri->path); + memcpy (buffer, uri->path, n+1); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + else { + int n = snprintf (buffer, remaining, "/%s", uri->path); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + } + + if (uri->query) { + int n = snprintf (buffer, remaining, "?%s", uri->query); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + if (uri->fragment) { + int n = snprintf (buffer, remaining, "#%s", uri->fragment); + buffer += n; + remaining -= n; + if (remaining < 10) { + free (buffer); + return NULL; + } + } + + /* Free only GString not data contained, return the data instead */ + return b; +} + + +/** + * gnet_uri_set_scheme + * @uri: a #GURI + * @scheme: scheme + * + * Sets a #GURI's scheme. + * + **/ +void +gnet_uri_set_scheme (GURI* uri, const char* scheme) +{ + if (!uri) { + return; + } + + if (uri->scheme) + { + free (uri->scheme); + uri->scheme = NULL; + } + + if (scheme) + uri->scheme = strdup (scheme); +} + + +/** + * gnet_uri_set_userinfo + * @uri: a #GURI + * @userinfo: user info + * + * Sets a #GURI's user info. + * + **/ +void +gnet_uri_set_userinfo (GURI* uri, const char* user, const char* passwd) +{ + if (!uri) { + return; + } + + if (uri->user) + { + free (uri->user); + uri->user = NULL; + } + if (uri->passwd) + { + free (uri->passwd); + uri->passwd = NULL; + } + + if (user) + uri->user = strdup (user); + if (passwd) + uri->passwd = strdup (passwd); +} + + +/** + * gnet_uri_set_hostname + * @uri: a #GURI + * @hostname: host name + * + * Sets a #GURI's host name. + * + **/ +void +gnet_uri_set_hostname (GURI* uri, const char* hostname) +{ + if (!uri) { + return; + } + + if (uri->hostname) + { + free (uri->hostname); + uri->hostname = NULL; + } + + if (hostname) + uri->hostname = strdup (hostname); +} + + +/** + * gnet_uri_set_port + * @uri: a #GURI + * @port: port + * + * Set a #GURI's port. + * + **/ +void +gnet_uri_set_port (GURI* uri, int port) +{ + uri->port = port; +} + + +/** + * gnet_uri_set_path + * @uri: a #GURI + * @path: path + * + * Set a #GURI's path. + * + **/ +void +gnet_uri_set_path (GURI* uri, const char* path) +{ + if (!uri) { + return; + } + + if (uri->path) + { + free (uri->path); + uri->path = NULL; + } + + if (path) + uri->path = strdup (path); +} + + + +/** + * gnet_uri_set_query + * @uri: a #GURI + * @query: query + * + * Set a #GURI's query. + * + **/ +void +gnet_uri_set_query (GURI* uri, const char* query) +{ + if (!uri) { + return; + } + + if (uri->query) + { + free (uri->query); + uri->query = NULL; + } + + if (query) + uri->query = strdup (query); +} + + +/** + * gnet_uri_set_fragment + * @uri: a #GURI + * @fragment: fragment + * + * Set a #GURI's fragment. + * + **/ +void +gnet_uri_set_fragment (GURI* uri, const char* fragment) +{ + if (!uri) { + return; + } + + if (uri->fragment) + { + free (uri->fragment); + uri->fragment = NULL; + } + + if (fragment) + uri->fragment = strdup (fragment); +} + + +/** + * gnet_mms_helper + * @uri: a #GURI + * + * returns char* representation of an uri that is sutable for + * using in mms protocol. + * '/path?query' + * + **/ + +char* gnet_mms_helper(const GURI* uri, int make_absolute) +{ + size_t len = 0; + char *ret, *tmp = NULL; + + + /* Strip leading slashes and calculate the length of the path + * which might not be present in the URI */ + if (uri->path) { + tmp = uri->path; + while (*tmp == '/') + ++tmp; + len += strlen(tmp); + } + /* Append length of the query part */ + if (uri->query) + len += strlen(uri->query) + 1; /* + '?' */ + + if (!(ret = (char *) malloc(len + 2))) + return NULL; + memset (ret, 0, len + 2); + + if (make_absolute) + strcpy(ret, "/"); + else + ret[0] = 0; + + /* Copy the optional path */ + if (tmp) + strcat(ret, tmp); + + /* Copy the optional query */ + if (uri->query) { + strcat(ret, "?"); + strcat(ret, uri->query); + } + + return ret; +} diff --git a/plugins/mms/libmms/uri.h b/plugins/mms/libmms/uri.h new file mode 100644 index 00000000..75c96779 --- /dev/null +++ b/plugins/mms/libmms/uri.h @@ -0,0 +1,92 @@ +/* GNet - Networking library + * Copyright (C) 2000-2001 David Helder, David Bolcsfoldi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef _GNET_URI_H +#define _GNET_URI_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/** + * GURI: + * @scheme: Scheme (or protocol) + * @userinfo: User info + * @hostname: Host name + * @port: Port number + * @path: Path + * @query: Query + * @fragment: Fragment + * + * The #GURI structure represents a URI. All fields in this + * structure are publicly readable. + * + **/ +typedef struct _GURI GURI; + +struct _GURI +{ + char* scheme; + char* user; + char* passwd; + char* hostname; + int port; + char* path; + char* query; + char* fragment; +}; + + + +GURI* gnet_uri_new (const char* uri); +GURI* gnet_uri_new_fields (const char* scheme, const char* hostname, + const int port, const char* path); +GURI* +gnet_uri_new_fields_all (const char* scheme, const char* user, + const char* passwd, const char* hostname, + const int port, const char* path, + const char* query, const char* fragment); +GURI* gnet_uri_clone (const GURI* uri); +void gnet_uri_delete (GURI* uri); + +int gnet_uri_equal (const char * p1, const char * p2); +unsigned int gnet_uri_hash (const char * p); + +void gnet_uri_escape (GURI* uri); +void gnet_uri_unescape (GURI* uri); + +char* gnet_uri_get_string (const GURI* uri); + +void gnet_uri_set_scheme (GURI* uri, const char* scheme); +void gnet_uri_set_userinfo (GURI* uri, const char* user, const char* passwd); +void gnet_uri_set_hostname (GURI* uri, const char* hostname); +void gnet_uri_set_port (GURI* uri, int port); +void gnet_uri_set_path (GURI* uri, const char* path); +void gnet_uri_set_query (GURI* uri, const char* query); +void gnet_uri_set_fragment (GURI* uri, const char* fragment); + +char* gnet_mms_helper(const GURI* uri, int make_absolute); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _GNET_URI_H */ diff --git a/plugins/mms/mms.c b/plugins/mms/mmsplug.c index 4ad57832..4ad57832 100644 --- a/plugins/mms/mms.c +++ b/plugins/mms/mmsplug.c |