commit 7c84ea438573a1c5ecba5a4c31cda9316684ef9e Author: umjammer Date: Sun Jun 9 09:35:27 2019 +0900 initial imports diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..60549be --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/src/main/java/org/gjt/convert/binhex/BinHex4InputStream.java b/src/main/java/org/gjt/convert/binhex/BinHex4InputStream.java new file mode 100755 index 0000000..ec0fa72 --- /dev/null +++ b/src/main/java/org/gjt/convert/binhex/BinHex4InputStream.java @@ -0,0 +1,508 @@ +/* + JBinHex + Copyright (C) 2000, Erwin Bolwidt + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +package org.gjt.convert.binhex; + +import java.util.*; +import java.io.*; + +/** + This class completely decodes a BinHex4 file in three parts: the header, + the data fork and the resource fork. + By default, all the InputStream-derived methods work on the data fork. With + the methods useDataFork and useResourceFork the caller + can select from which fork this stream will read. However, because this stream + obtains the BinHex4 file through a stream, one can only read the forks in the + order that they are in the BinHex4 file, which is: first the data fork, then + the resource fork. A future version of this class that obtains it's data from + a RandomAccessFile may be able to switch to reading from data and resource + fork at any time. + +

+ Lacks something that translates between Unicode and the Mac character + set to deal with foreign characters in filenames, types and creators. + Anyone who has such a thing under a GPL license is invited to send it to me. + + @author Erwin Bolwidt + */ +public class BinHex4InputStream extends InputStream { + private final static int stateBeforeHeader = 0; + private final static int stateInDataFork = 1; + private final static int stateInResourceFork = 2; + private final static int stateError = 3; + + /** + Representation of a BinHex4 header section. + */ + public static class Header + { + /** + The name that this file had before encoding in BinHex4. The bytes + represent characters in the Macintosh character set. + */ + byte[] fileName; + + /** + The version of this file. Usually 0. This is a special feature of + the Macintosh file system that is, to my knowledge, infrequently + used. + */ + int version; + + /** + Four bytes containing the file type. The bytes represent + characters in the Macintosh character set. + */ + byte[] type; + + /** + Four bytes containing the creator type. The bytes represent + characters in the Macintosh character set. + */ + byte[] creator; + + /** + Macintosh file system file info flags + */ + int flags; + /** + Length of the data fork.

+ A java long even though it's only 32 bits in the file because I + don't know if it's signed or unsigned in the file. + */ + long dataLength; + /** + Length of the resource fork.

+ A java long even though it's only 32 bits in the file because I + don't know if it's signed or unsigned in the file. + */ + long resourceLength; + + /** + Reads a header from a completely 8-bit clean BinHex4-Hqx8 stream. + (No RLE coding allowed) + */ + public Header(InputStream in) throws IOException + { + int szFileName = in.read(); + if(szFileName == -1) + throw new EOFException("In Hqx header"); + fileName = new byte[szFileName]; + if(in.read(fileName) != szFileName) + throw new EOFException("In Hqx header"); + + version = in.read(); + if(version == -1) + throw new EOFException("In Hqx header"); + + type = new byte[4]; + if(in.read(type) != 4) + throw new EOFException("In Hqx header"); + + creator = new byte[4]; + if(in.read(creator) != 4) + throw new EOFException("In Hqx header"); + + flags = read16bits(in); + + dataLength = read32bits(in); + resourceLength = read32bits(in); + } + + /** + Returns the name this file had before encoding in BinHex. +

+ Since I've found no trace of a Macintosh character set converted + to/from Unicode, this method converts using the locale-default + character converter. This will usually only work well if your + character set is ASCII for character codes 0-127. + If anyone can send a converter for Mac characters to Unicode, I'll + put that in here. + */ + public String getFileName() + { + return new String(fileName); + } + + /** + Returns the type that this file had before encoding in BinHex. +

+ Since I've found no trace of a Macintosh character set converted + to/from Unicode, this method converts using the locale-default + character converter. This will usually only work well if your + character set is ASCII for character codes 0-127. + If anyone can send a converter for Mac characters to Unicode, I'll + put that in here. + */ + public String getType() + { + return new String(type); + } + + /** + Returns the creator that this file had before encoding in BinHex. +

+ Since I've found no trace of a Macintosh character set converted + to/from Unicode, this method converts using the locale-default + character converter. This will usually only work well if your + character set is ASCII for character codes 0-127. + If anyone can send a converter for Mac characters to Unicode, I'll + put that in here. + */ + public String getCreator() + { + return new String(creator); + } + + /** + Returns the version that this file had before encoding in BinHex. + */ + public int getVersion() + { + return version; + } + + /** + Returns the file info flags that this file had before encoding in BinHex. + */ + public int getFlags() + { + return flags; + } + + /** + Returns the length of the data fork. + */ + public long getDataLength() + { + return dataLength; + } + + /** + Returns the length of the resource fork. + */ + public long getResourceLength() + { + return resourceLength; + } + + /** + Returns the content of this header in a single informational String. + */ + public String toString() + { + return "BinHex4InputStream.Header[\nfileName = " + new String(fileName) + + "\nversion = " + version + "\ntype = " + new String(type) + + "\ncreator = " + new String(creator) + "\nflags = " + flags + + "\ndataLength = " + dataLength + "\nresourceLength = " + resourceLength + + "\n]"; + } + } + + + /** + Constructs a BinHex4InputStream from a stream that is a source of 7-bit + Hqx7 encoded data. This is the typical use for files fetched from the + Internet or through e-mail. + */ + public BinHex4InputStream(InputStream source) + { + hqxIn = new RLE_CRCInputStream(source); + } + + /** + Constructs a BinHex4InputStream from a stream that is either a source + of 7-bit Hqx7 encoded data or of pure 8-bit data in Hqx8 format. The + flag eightBit tells this class what to expect. + + @param source + the data source + @param eightBit + if true, the data source must supply 8-bit data in Hqx8 format. + If false, the data source must supply 7-bit data in Hqx7 format. + */ + public BinHex4InputStream(InputStream source, boolean eightBit) + { + hqxIn = new RLE_CRCInputStream(source, eightBit); + } + + /** + Returns the header section of this BinHex file in a Header object. + */ + public Header getHeader() throws IOException + { + if(header == null) + readHeader(); + return header; + } + + private void readHeader() throws IOException + { + try { + if(streamState != stateBeforeHeader) + throw new IOException("Wrong stream state, cannot read the header now."); + hqxIn.resetCRC(); + header = new Header(hqxIn); + checkDataCRC(); + switchState(stateInDataFork); + } catch(IOException e) + { + switchState(stateError); + throw e; + } + } + + private void switchState(int newState) + { + if((header == null && newState != stateError) || streamState == stateError) + throw new IllegalStateException( + "Cannot switch state with header == null or in errorState"); + + if(newState == stateInDataFork) + { + bytesLeftInFork = header.dataLength; + hqxIn.resetCRC(); + hardEndOfFork = false; + seenEndOfFork = false; + } + else if(newState == stateInResourceFork) + { + bytesLeftInFork = header.resourceLength; + hqxIn.resetCRC(); + hardEndOfFork = false; + seenEndOfFork = false; + } + streamState = newState; + } + + /** + Switch reading from the data fork. All methods derived from InputStream + will apply to the data fork. This method cannot be called after any + method has thrown an IOException, or after useResourceFork has been called. + */ + public void useDataFork() throws IOException + { + if(streamState == stateError) + throw new IOException("Stream is already in error state"); + else if(streamState == stateBeforeHeader) + readHeader(); + else if(streamState == stateInResourceFork) + throw new IOException( + "Sorry, no random access. Cannot switch back from " + + "resource to data fork."); + else if(streamState != stateInDataFork) + throw new IllegalStateException("Stream is in unknown state."); + } + + /** + Swtich to reading from the resource fork. All methods derived from + InputStream will apply to the resource fork. This method cannot be + called after any method has thrown an IOException. + */ + public void useResourceFork() throws IOException + { + if(streamState == stateError) + throw new IOException("Stream is already in error state"); + else if(streamState == stateBeforeHeader) + readHeader(); + + if(streamState == stateInDataFork) + { + skipToEndOfFork(); + switchState(stateInResourceFork); + } + + else if(streamState != stateInResourceFork) + throw new IllegalStateException("Stream is in unexpected state."); + } + + public int read() throws IOException + { + if(streamState == stateBeforeHeader) + useDataFork(); + + if(seenEndOfFork) + { + // If this method is called a second time AFTER a -1, the + // caller must know for itself and be ready to get an EOFException. + // So set seenEOF to false again, and nextDecodedByte will throw + // one of these. + seenEndOfFork = false; + return -1; + } else if(hardEndOfFork) + throw new EOFException("End of fork"); + + if(bytesLeftInFork == 0) + { + hardEndOfFork = true; + return -1; + } + + int b = hqxIn.read(); + if(b == -1) + throw new EOFException("Physical end-of-file before end of fork"); + + bytesLeftInFork--; + + if(bytesLeftInFork == 0) + { + // The fork was completely read, now check the fork's CRC + checkDataCRC(); + } + return b; + } + + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + public int read(byte[] b, int off, int len) throws IOException + { + if(seenEndOfFork) + { + // If this method is called a second time AFTER a -1, the + // caller must know for itself and be ready to get an EOFException. + // So set seenEOF to false again, and nextDecodedByte will throw + // one of these. + seenEndOfFork = false; + return -1; + } else if(hardEndOfFork) + throw new EOFException("End of fork"); + + if(bytesLeftInFork == 0) + { + hardEndOfFork = true; + return -1; + } + + if(len == 0) + return 0; + + int toRead = (len > bytesLeftInFork) ? (int)bytesLeftInFork : len; + + int r = hqxIn.read(b, off, toRead); + if(r <= 0) + throw new EOFException("Physical end-of-file before end of fork"); + + bytesLeftInFork -= r; + + if(bytesLeftInFork <= 0) + { + // The fork was completely read, now check the fork's CRC + checkDataCRC(); + } + return r; + } + + private void checkDataCRC() throws IOException + { + int calculatedCRC = hqxIn.getCRC(); + int readCRC = read16bits(hqxIn); + if(calculatedCRC != readCRC) + throw new IOException("Incorrect CRC (calculated:"+calculatedCRC+" != file:"+readCRC+")"); + } + + private void skipToEndOfFork() throws IOException + { + skip(bytesLeftInFork); + } + + private static long read32bits(InputStream in) throws IOException + { + long res = 0; + for(int i = 0; i < 4; i++) + { + int v = in.read(); + if(v == -1) + throw new EOFException("Unexpected"); + res = (res << 8) | v; + } + return res; + } + + private static int read16bits(InputStream in) throws IOException + { + int fl1 = in.read(); + if(fl1 == -1) + throw new EOFException("Unexpected"); + int fl2 = in.read(); + if(fl2 == -1) + throw new EOFException("Unexpected"); + return (fl1 << 8) | fl2; + } + + public static void main(String[] args) + { + try { + BinHex4InputStream in = new BinHex4InputStream(System.in); + System.err.println(in.getHeader()); + + byte[] buf = new byte[1024]; + + System.err.println("Starting to convert"); + while(true) + { + int r = in.read(buf); + if(r <= 0) + return; + System.out.write(buf, 0, r); + } + } catch(IOException e) + { + e.printStackTrace(); + } + } + + /** + One of three states: before header, in data fork, or in resource fork. + */ + private int streamState = stateBeforeHeader; + + /** + How many bytes are left to read in the current fork; only valid when + streamState != beforeHeader + */ + private long bytesLeftInFork; + + /** + The header of the BinHex4 file; only available when streamState != beforeHeader + */ + private Header header; + + /** + The input stream, conveniently cast to type RLE_CRCInputStream so the CRC + related methods can be easily called. + */ + private RLE_CRCInputStream hqxIn; + + /** + read(byte[]) sets this if it cannot return -1 immediately. Read calls + must return -1 and set this flag to false when they find this flag true. + */ + private boolean seenEndOfFork; + + /** + Hard end of fork. If somebody reads from this stream after they've been + returned a -1, which means seenEndOfFork == false and hardEndOfFork == true, + then they get an EOFException. + */ + private boolean hardEndOfFork; +} + diff --git a/src/main/java/org/gjt/convert/binhex/DeBinHex.java b/src/main/java/org/gjt/convert/binhex/DeBinHex.java new file mode 100755 index 0000000..e3ff181 --- /dev/null +++ b/src/main/java/org/gjt/convert/binhex/DeBinHex.java @@ -0,0 +1,192 @@ +/* + JBinHex + Copyright (C) 2000, Erwin Bolwidt + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +package org.gjt.convert.binhex; + +import java.util.*; +import java.io.*; +import java.net.*; + +/** + Command line program to decode binhex files from the harddisk or from + the web. + + It accepts the following command line parameters:

+ + + +
  • Either -u <url> or -f <file> + to specify the source BinHexed file. If neither of those options + is present, DeBinHex reads stdin. + +
  • -d to decode the data fork. It will be put in + the file with the name that came from the BinHex header. + +
  • -df <filename> to decode the data fork + to the named file instead of the name that came from the BinHex + header. + +
  • -r to decode the resource fork. It will be put + in the file with the name that came from the BinHex header, with + the extension ".resource" appended to + it. + +
  • -rf <filename> to decode the resource + fork to the named file instead of the name that came from the + BinHex header. + +
  • Both -d/-df options and -r/-rf + may be present at the same time. If none of these options is + present, DeBinHex will decode the data fork as if + the -d options was specified. + +
  • -h to only show the header of the BinHex file + on stdout. The decoding options are ignored. + +
  • + + @author Erwin Bolwidt + */ +public class DeBinHex +{ + + public static void main(String[] args) throws Exception + { + String inFile = findValueOption("-f", args); + InputStream binhexIn = System.in; + if(inFile != null) + binhexIn = new FileInputStream(inFile); + else + { + String urlString = findValueOption("-u", args); + if(urlString != null) + { + URL url = new URL(urlString); + binhexIn = url.openConnection().getInputStream(); + } + } + + if(findOption("-h", args)) + { + action(binhexIn, true, false, null, false, null); + return; + } + String dataFile = null; + String resourceFile = null; + boolean doData = false; + boolean doResource = false; + if((dataFile = findValueOption("-df", args)) != null) + doData = true; + if(findOption("-d", args)) + doData = true; + if((resourceFile = findValueOption("-rf", args)) != null) + doResource = true; + if(findOption("-r", args)) + doResource = true; + if(!doResource && !doData) + // The user didn't specify anything to do, so let's do the data + // fork. + doData = true; + action(binhexIn, false, doData, dataFile, doResource, resourceFile); + } + + private static String findValueOption(String name, String[] args) + { + for(int i = 0; i < args.length; i++) + { + if(args[i].equals(name)) + { + if(i + 1 < args.length) + return args[i+1]; + else + throw new RuntimeException( + "Cannot use option that needs an argument " + + "as the last option"); + } + } + return null; + } + + private static boolean findOption(String name, String[] args) + { + for(int i = 0; i < args.length; i++) + { + if(args[i].equals(name)) + return true; + } + return false; + } + + public static void action( + InputStream binhexIn, boolean justHeader, + boolean doData, String dataOut, + boolean doResource, String resourceOut) throws IOException + { + BinHex4InputStream binhex; + + binhex = new BinHex4InputStream(binhexIn); + if(justHeader) + { + System.out.println(binhex.getHeader()); + return; + } + + String fileName = binhex.getHeader().getFileName(); + + if(doData && dataOut == null) + dataOut = fileName; + + if(doResource && resourceOut == null) + resourceOut = fileName.concat(".resource"); + + if(doData) + { + FileOutputStream out = new FileOutputStream(dataOut); + try { + pump(binhex, out); + } finally { + out.close(); + } + } + + if(doResource) + { + FileOutputStream out = new FileOutputStream(resourceOut); + binhex.useResourceFork(); + try { + pump(binhex, out); + } finally { + out.close(); + } + } + } + + private static void pump(InputStream in, OutputStream out) throws IOException + { + byte[] buf = new byte[1024]; + while(true) + { + int r = in.read(buf); + if(r <= 0) + return; + out.write(buf, 0, r); + } + } + +} diff --git a/src/main/java/org/gjt/convert/binhex/Hqx7_to_Hqx8InputStream.java b/src/main/java/org/gjt/convert/binhex/Hqx7_to_Hqx8InputStream.java new file mode 100755 index 0000000..d170e43 --- /dev/null +++ b/src/main/java/org/gjt/convert/binhex/Hqx7_to_Hqx8InputStream.java @@ -0,0 +1,363 @@ +/* + JBinHex + Copyright (C) 2000, Erwin Bolwidt + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +package org.gjt.convert.binhex; + +import java.util.*; +import java.io.*; + +/** + Converts a 7-bit encoded binhex4.0 data stream to a 8-bit encoded + data stream. The 8-bit stream still needs to be split in a data and + a resource fork, and still needs to have any Run-Length Encoded (RLE) + sequences expanded. +

    + This class does not (yet) support segmented files. It + is however very liberal about whitespace: any is allowed. So you could cat + all segmented files together and remove the "--- end of part NN ---" and + the beginning-of-next-part identifier "---" lines from the resulting file + before decoding. + + @author Erwin Bolwidt + */ +public class Hqx7_to_Hqx8InputStream extends FilterInputStream { + + final static String validChars + = "!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`" + + "abcdefhijklmpqr"; + + final static String binhexHeaderId = "(This file must be converted with BinHex"; + + /** + Although encoded in 7-bit, each value only represents 6 bits. This + is because not all 7-bit values can be used since they may have special + meanings to the underlying transport mechanism. Compare Hqx7 with Base64, + they have the same design goals although Base64 probably achieves them + better. + */ + final static byte[] sixBitTable = new byte[256]; + final static byte invalidEntry = (byte)64; + + /** + The size of the streambuffer. Could be anything that's not too small. + Bigger sizes probably improve performance, up to a point. + */ + final static int sz_streamBuf = 1024; + + /** + Initializes six-bit table from the validChars string. + */ + static { + // An assertion to see if nobody mistakenly changed the validChars + // string. This whole class wouldn't work if they had. + if(validChars.length() != 64) + throw new IllegalStateException( + "Incorrect class, the static validChars entry should " + + "be 64 characters long."); + for(int i = 0; i < sixBitTable.length; i++) + { + // 64 is the 'invalid character' value + sixBitTable[i] = invalidEntry; + } + // Now give every valid character the 6-bit + for(int i = 0, l = validChars.length(); i < l; i++) + { + sixBitTable[validChars.charAt(i)] = (byte)i; + } + + } + + /** + Constructs a Hqx7_to_Hqx8InputStream that reads from the supplied source + and converts that source to 8-bit Hqx8 without RLE expansion. + */ + public Hqx7_to_Hqx8InputStream(InputStream source) + { + super(source); + } + + private void skipHeader() throws IOException + { + int b, c = nextStreamByte(); + + while(true) + { + for(int i = 0, l = binhexHeaderId.length(); c != -1; c = nextStreamByte(), i++) + { + if(i == l) { + skipHeaderAfterId(); + return; + } + if(c != binhexHeaderId.charAt(i)) + // Try again at the next line. + break; + } + + if(c == -1) + throw new EOFException("Couldn't find start of Hqx7 part"); + + do { + b = nextStreamByte(); + if(b == -1) + throw new EOFException("Couldn't find start of Hqx7 part"); + } while(b != '\n' && b != '\r'); + + c = nextStreamByte(); + // Allow MS-DOS type linebreaks too + if(c == '\n' && b == '\r') + c = nextStreamByte(); + + } + } + + private void skipHeaderAfterId() throws IOException + { + int b; + + do { + b = nextStreamByte(); + if(b == -1) + throw new EOFException("Couldn't find start of Hqx7 part"); + } while(b != '\n' && b != '\r'); + + do { + b = nextStreamByte(); + if(b == -1) + throw new EOFException("Couldn't find start of Hqx7 part"); + } while(b != ':' && Character.isWhitespace((char)b)); + + if(b != ':') + throw new EOFException("Invalid start of Hqx7 part, no : right after id line"); + } + + /** + Internal method to get the next physical byte from the superclass + stream, while keeping buffering consistent. + + @return + the byte that was read, or -1 if end-of-file was encountered for + the first time. Any subsequent read throws an EOFException. + @exception EOFException + thrown when this method is called after it returned -1 once. + */ + private int nextStreamByte() throws IOException + { + if(sbFilled == -1) + throw new EOFException("End of file already reached."); + + if(sbIndex < sbFilled) + { +// Debug line +// System.err.print((char)streamBuffer[sbIndex]); + // Take care to return no sign-extended numbers, hence the & 0xff + return ((int)streamBuffer[sbIndex++]) & 0xff; + } + + sbFilled = super.read(streamBuffer, 0, streamBuffer.length); + if(sbFilled <= 0) + { + sbFilled = -1; +// Debug line +// new Exception().printStackTrace(); + return sbFilled; + } + + sbIndex = 0; +// Debug line +// System.err.print((char)streamBuffer[sbIndex]); + // Take care to return no sign-extended numbers, hence the & 0xff + return ((int)streamBuffer[sbIndex++]) & 0xff; + } + + private int next6bits() throws IOException + { + if(hardEOF) + throw new EOFException( + "All Hqx7 data was read and a soft EOF was already " + + "reported with a -1 return to read()."); + + int b; + do { + b = nextStreamByte(); + // True end-of-file is not allowed, a Hqx7 always ends earlier + // with a : character. + if(b == -1) + throw new EOFException( + "EOF reached before closing : character. " + + "Possible data corruption."); + + // The high bit could have been used as a parity bit. Better be sure. + b &= 0x7f; + + // The : character terminates the stream + if(b == ':') { + hardEOF = true; + return -1; + } + + } while(Character.isWhitespace((char)b)); + + int v = sixBitTable[b]; + if(v == invalidEntry) + throw new IOException( + "Illegal character in Hqx7 stream encountered, " + + "possible data corruption. ('" + (char)b + "')"); + return v; + } + + private int nextDecodedByte() throws IOException + { + while(bitsLeft < 8) + { + int bits = next6bits(); + if(bits == -1) + return -1; + bitBuffer = (bitBuffer << 6) | bits; + bitsLeft += 6; + } + // Taking 8 bits out. + bitsLeft -= 8; + // Incidentally, the bitBuffer value also needs a shift of the number + // of bits left after taking 8 bits out. + return (bitBuffer >>> bitsLeft) & 0xff; + } + + public int read() throws IOException + { + if(!headerDone) + { + skipHeader(); + headerDone = true; + } + + if(seenEOF) + { + // If this method is called a second time AFTER a -1, the + // caller must know for itself and be ready to get an EOFException. + // So set seenEOF to false again, and nextDecodedByte will throw + // one of these. + seenEOF = false; + return -1; + } + return nextDecodedByte(); + } + + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + public int read(byte[] b, int off, int len) throws IOException + { + if(!headerDone) + { + skipHeader(); + headerDone = true; + } + + if(seenEOF) + { + // If this method is called a second time AFTER a -1, the + // caller must know for itself and be ready to get an EOFException. + // So set seenEOF to false again, and nextDecodedByte will throw + // one of these. + seenEOF = false; + return -1; + } + + for(int i = off, max = off+len; i < max; i++) + { + int t = nextDecodedByte(); + if(t == -1) { + if(i == off) + // No data read yet, so safe to return -1 + return -1; + seenEOF = true; + return i - off; + } + b[i] = (byte)t; + } + return len; + } + + + public static void main(String[] args) + { + try { + InputStream in = new Hqx7_to_Hqx8InputStream(System.in); + byte[] buf = new byte[1024]; + + System.err.println("Starting to convert"); + while(true) + { + int r = in.read(buf); + if(r <= 0) + return; + System.out.write(buf, 0, r); + } + } catch(IOException e) + { + e.printStackTrace(); + } + } + + /** + A buffer for a maximum of two times six bits. + */ + private int bitBuffer; + /** + How many bits are left in the bitBuffer. + */ + private int bitsLeft; + + /** + Bytes that were read from the stream but not all yet processed. + */ + private byte[] streamBuffer = new byte[sz_streamBuf]; + + /** + The next byte in the stream buffer to process. + */ + private int sbIndex = 0; + + /** + How many bytes in the buffer are stream data; the buffer could + be longer than that. + */ + private int sbFilled = 0; + + /** + The read(byte[], int, int) call cannot immediately signal eof with a -1 + when it sees it, since it may already have read some data. So it sets + this flag, which it checks the next time it is called. + */ + private boolean seenEOF = false; + + /** + This flag is set by next6bits after it's seen a : delimiter. Any time + thereafter that next6bits is called, it will throw EOFException. + */ + private boolean hardEOF = false; + + /** + This flag is true when the header has been read + */ + private boolean headerDone = false; + +} diff --git a/src/main/java/org/gjt/convert/binhex/RLE_CRCInputStream.java b/src/main/java/org/gjt/convert/binhex/RLE_CRCInputStream.java new file mode 100755 index 0000000..f05b9fa --- /dev/null +++ b/src/main/java/org/gjt/convert/binhex/RLE_CRCInputStream.java @@ -0,0 +1,321 @@ +/* + JBinHex + Copyright (C) 2000, Erwin Bolwidt + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +package org.gjt.convert.binhex; + +import java.util.*; +import java.io.*; + +/** + Decodes Run-length encoding LE from a 8-bit BinHex stream and calculates + CRCs (Cyclic Redundancy Check) from the decoded bytes as it passes them to + the caller. + + @author Erwin Bolwidt + */ +public class RLE_CRCInputStream extends FilterInputStream { + /** + The size of the streambuffer. Could be anything that's not too small. + Bigger sizes probably improve performance, up to a point. + */ + final static int sz_streamBuf = 1024; + + /** + This character signals that the previous character must be repeated + a number of times (Run-Length Encoding), unless the character after + it is 0, in which case it simply means the literal 0x90 character. + */ + final static int rleChar = 0x90; + + /** + Constructs a RLE_CRCInputStream from a stream that is a source of 7-bit + Hqx7 encoded data. This is the typical use for files fetched from the + Internet or through e-mail. Internally, a Hqx7_to_Hqx8InputStream is + created to translate from 7-bit to 8-bit format before passing the data + through this stream. + */ + public RLE_CRCInputStream(InputStream source) + { + super(new Hqx7_to_Hqx8InputStream(source)); + } + + /** + Constructs a RLE_CRCInputStream from a stream that is either a source + of 7-bit Hqx7 encoded data or of pure 8-bit data in Hqx8 format. The + flag eightBit tells this class what to expect. + + @param source + the data source + @param eightBit + if true, the data source must supply 8-bit data in Hqx8 format. + If false, the data source must supply 7-bit data in Hqx7 format. + */ + public RLE_CRCInputStream(InputStream source, boolean eightBit) + { + super(eightBit ? source : new Hqx7_to_Hqx8InputStream(source)); + } + + + /** + Internal method to get the next physical byte from the superclass + stream, while keeping buffering consistent. + @return + the byte that was read, or -1 if end-of-file was encountered for + the first time. Any subsequent read throws an EOFException. + @exception EOFException + thrown when this method is called after it returned -1 once. + */ + private int nextStreamByte() throws IOException + { + if(sbFilled == -1) + throw new EOFException("End of file already reached."); + + if(sbIndex < sbFilled) + // Take care to return no sign-extended numbers, hence the & 0xff + return ((int)streamBuffer[sbIndex++]) & 0xff; + + sbFilled = super.read(streamBuffer, 0, streamBuffer.length); + if(sbFilled <= 0) + { + sbFilled = -1; + return sbFilled; + } + + sbIndex = 0; + // Take care to return no sign-extended numbers, hence the & 0xff + return ((int)streamBuffer[sbIndex++]) & 0xff; + } + + private int nextDecodedByte() throws IOException + { + // Check if we're still busy expanding a run-length-encoding. + // No reads are necessary in that case. + if(inRLE) + { + // Being 'inRLE' means we should repeat 'lastByte' for 'rleRepeat' + // times, until rleRepeat is 0. + if(--rleRepeat <= 0) + inRLE = false; + // lastByte remains the same + updateCRC(lastByte); + return lastByte; + } + + // Read from the stream + int b = nextStreamByte(); + if(b == -1) + { + // Invalidate lastByte + lastByte = -1; + return -1; + } + + // Check if it's the start of a run-length-encoding. + // RLE's need special handling. + if(b == rleChar) + { + // Start of RLE + int c = nextStreamByte(); + if(c == -1) + throw new EOFException( + "Corrupted Hqx8 stream, EOF just after a " + + "0x90 RLE char."); + if(c == 0) + { + // No RLE, just a single 0x90 character + // lastByte must be set, because 0x90 could itself be + // subject to RLE expansion. + lastByte = rleChar; + updateCRC(rleChar); + return rleChar; + } + + // Repeat minus two because: the first one was when it + // the byte was returned normally, and the second time is now. + rleRepeat = c - 2; + if(rleRepeat > 0) + inRLE = true; + // lastByte remains the same + updateCRC(lastByte); + return lastByte; + } + else + { + // Plain old straight data passing through. Do remember this + // as the lastByte though, because the next character could + // be the RLE char which means that _this_ character needs + // to be repeated. + lastByte = b; + updateCRC(b); + return b; + } + } + + private void updateCRC(int b) + { + // This is from Peter Lewis' article. It's probably very inefficient + // so if anyone can give me a better version, I'd be much obliged. + boolean temp; + for(int i = 0 ; i < 8; i++) + { + temp = (calculatedCRC & 0x8000) != 0; + calculatedCRC = (calculatedCRC << 1) | (b >> 7); + if(temp) + calculatedCRC ^= 0x1021; + b = (b << 1) & 0xff; + } + } + + /** + Resets the calculated CRC to zero. If your file contains multiple sections, + as the BinHex4 format does, you must reset it before switching to a new + section if you want to calculate CRCs for that section. + */ + public void resetCRC() + { + calculatedCRC = 0; + } + + /** + Returns the CRC calculated over the data that was read since the beginning + of the stream or since the last call to resetCRC. + Must only be called after all the data was read in a section, because + the CRC is updated as if two extra 0 bytes were read. This is dictated + by the BinHex4 protocol. + */ + public int getCRC() + { + updateCRC(0); + updateCRC(0); + return calculatedCRC & 0xffff; + } + + public int read() throws IOException + { + if(seenEOF) + { + // If this method is called a second time AFTER a -1, the + // caller must know for itself and be ready to get an EOFException. + // So set seenEOF to false again, and nextDecodedByte will throw + // one of these. + seenEOF = false; + return -1; + } + return nextDecodedByte(); + } + + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + public int read(byte[] b, int off, int len) throws IOException + { + if(seenEOF) + { + // If this method is called a second time AFTER a -1, the + // caller must know for itself and be ready to get an EOFException. + // So set seenEOF to false again, and nextDecodedByte will throw + // one of these. + seenEOF = false; + return -1; + } + + for(int i = off, max = off+len; i < max; i++) + { + int t = nextDecodedByte(); + if(t == -1) { + if(i == off) + // No data read yet, so safe to return -1 + return -1; + seenEOF = true; + return i - off; + } + b[i] = (byte)t; + } + return len; + } + + + public static void main(String[] args) + { + try { + InputStream in = new RLE_CRCInputStream(System.in); + byte[] buf = new byte[1024]; + + System.err.println("Starting to convert"); + while(true) + { + int r = in.read(buf); + if(r <= 0) + return; + System.out.write(buf, 0, r); + } + } catch(IOException e) + { + e.printStackTrace(); + } + } + + /** + True if private method nextDecodedByte is still repeating a character + that was part of a Run-Length Encoding sequence. + */ + private boolean inRLE; + + /** + The last byte that was returned by nextDecodedByte. When nextDecodedByte + encounters a RLE-repeat code, it repeats this character a number of times. + */ + private int lastByte; + + /** + If inRLE is true, then nextDecodedByte will return the byte + lastByte another rleRepeat times. + */ + private int rleRepeat; + + /** + Bytes that were read from the stream but not all yet processed. + */ + private byte[] streamBuffer = new byte[sz_streamBuf]; + + /** + The next byte in the stream buffer to process. + */ + private int sbIndex = 0; + + /** + How many bytes in the buffer are stream data; the buffer could + be longer than that. + */ + private int sbFilled = 0; + + /** + The read(byte[], int, int) call cannot immediately signal eof with a -1 + when it sees it, since it may already have read some data. So it sets + this flag, which it checks the next time it is called. + */ + private boolean seenEOF = false; + + /** + The CRC value that is being calculated. + */ + private int calculatedCRC; +}