Byte Order Mark Screws Up File Reading in Java

Byte order mark screws up file reading in Java

EDIT: I've made a proper release on GitHub: https://github.com/gpakosz/UnicodeBOMInputStream


Here is a class I coded a while ago, I just edited the package name before pasting. Nothing special, it is quite similar to solutions posted in SUN's bug database. Incorporate it in your code and you're fine.

/* ____________________________________________________________________________
*
* File: UnicodeBOMInputStream.java
* Author: Gregory Pakosz.
* Date: 02 - November - 2005
* ____________________________________________________________________________
*/
package com.stackoverflow.answer;

import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;

/**
* The <code>UnicodeBOMInputStream</code> class wraps any
* <code>InputStream</code> and detects the presence of any Unicode BOM
* (Byte Order Mark) at its beginning, as defined by
* <a href="http://www.faqs.org/rfcs/rfc3629.html">RFC 3629 - UTF-8, a transformation format of ISO 10646</a>
*
* <p>The
* <a href="http://www.unicode.org/unicode/faq/utf_bom.html">Unicode FAQ</a>
* defines 5 types of BOMs:<ul>
* <li><pre>00 00 FE FF = UTF-32, big-endian</pre></li>
* <li><pre>FF FE 00 00 = UTF-32, little-endian</pre></li>
* <li><pre>FE FF = UTF-16, big-endian</pre></li>
* <li><pre>FF FE = UTF-16, little-endian</pre></li>
* <li><pre>EF BB BF = UTF-8</pre></li>
* </ul></p>
*
* <p>Use the {@link #getBOM()} method to know whether a BOM has been detected
* or not.
* </p>
* <p>Use the {@link #skipBOM()} method to remove the detected BOM from the
* wrapped <code>InputStream</code> object.</p>
*/
public class UnicodeBOMInputStream extends InputStream
{
/**
* Type safe enumeration class that describes the different types of Unicode
* BOMs.
*/
public static final class BOM
{
/**
* NONE.
*/
public static final BOM NONE = new BOM(new byte[]{},"NONE");

/**
* UTF-8 BOM (EF BB BF).
*/
public static final BOM UTF_8 = new BOM(new byte[]{(byte)0xEF,
(byte)0xBB,
(byte)0xBF},
"UTF-8");

/**
* UTF-16, little-endian (FF FE).
*/
public static final BOM UTF_16_LE = new BOM(new byte[]{ (byte)0xFF,
(byte)0xFE},
"UTF-16 little-endian");

/**
* UTF-16, big-endian (FE FF).
*/
public static final BOM UTF_16_BE = new BOM(new byte[]{ (byte)0xFE,
(byte)0xFF},
"UTF-16 big-endian");

/**
* UTF-32, little-endian (FF FE 00 00).
*/
public static final BOM UTF_32_LE = new BOM(new byte[]{ (byte)0xFF,
(byte)0xFE,
(byte)0x00,
(byte)0x00},
"UTF-32 little-endian");

/**
* UTF-32, big-endian (00 00 FE FF).
*/
public static final BOM UTF_32_BE = new BOM(new byte[]{ (byte)0x00,
(byte)0x00,
(byte)0xFE,
(byte)0xFF},
"UTF-32 big-endian");

/**
* Returns a <code>String</code> representation of this <code>BOM</code>
* value.
*/
public final String toString()
{
return description;
}

/**
* Returns the bytes corresponding to this <code>BOM</code> value.
*/
public final byte[] getBytes()
{
final int length = bytes.length;
final byte[] result = new byte[length];

// Make a defensive copy
System.arraycopy(bytes,0,result,0,length);

return result;
}

private BOM(final byte bom[], final String description)
{
assert(bom != null) : "invalid BOM: null is not allowed";
assert(description != null) : "invalid description: null is not allowed";
assert(description.length() != 0) : "invalid description: empty string is not allowed";

this.bytes = bom;
this.description = description;
}

final byte bytes[];
private final String description;

} // BOM

/**
* Constructs a new <code>UnicodeBOMInputStream</code> that wraps the
* specified <code>InputStream</code>.
*
* @param inputStream an <code>InputStream</code>.
*
* @throws NullPointerException when <code>inputStream</code> is
* <code>null</code>.
* @throws IOException on reading from the specified <code>InputStream</code>
* when trying to detect the Unicode BOM.
*/
public UnicodeBOMInputStream(final InputStream inputStream) throws NullPointerException,
IOException

{
if (inputStream == null)
throw new NullPointerException("invalid input stream: null is not allowed");

in = new PushbackInputStream(inputStream,4);

final byte bom[] = new byte[4];
final int read = in.read(bom);

switch(read)
{
case 4:
if ((bom[0] == (byte)0xFF) &&
(bom[1] == (byte)0xFE) &&
(bom[2] == (byte)0x00) &&
(bom[3] == (byte)0x00))
{
this.bom = BOM.UTF_32_LE;
break;
}
else
if ((bom[0] == (byte)0x00) &&
(bom[1] == (byte)0x00) &&
(bom[2] == (byte)0xFE) &&
(bom[3] == (byte)0xFF))
{
this.bom = BOM.UTF_32_BE;
break;
}

case 3:
if ((bom[0] == (byte)0xEF) &&
(bom[1] == (byte)0xBB) &&
(bom[2] == (byte)0xBF))
{
this.bom = BOM.UTF_8;
break;
}

case 2:
if ((bom[0] == (byte)0xFF) &&
(bom[1] == (byte)0xFE))
{
this.bom = BOM.UTF_16_LE;
break;
}
else
if ((bom[0] == (byte)0xFE) &&
(bom[1] == (byte)0xFF))
{
this.bom = BOM.UTF_16_BE;
break;
}

default:
this.bom = BOM.NONE;
break;
}

if (read > 0)
in.unread(bom,0,read);
}

/**
* Returns the <code>BOM</code> that was detected in the wrapped
* <code>InputStream</code> object.
*
* @return a <code>BOM</code> value.
*/
public final BOM getBOM()
{
// BOM type is immutable.
return bom;
}

/**
* Skips the <code>BOM</code> that was found in the wrapped
* <code>InputStream</code> object.
*
* @return this <code>UnicodeBOMInputStream</code>.
*
* @throws IOException when trying to skip the BOM from the wrapped
* <code>InputStream</code> object.
*/
public final synchronized UnicodeBOMInputStream skipBOM() throws IOException
{
if (!skipped)
{
in.skip(bom.bytes.length);
skipped = true;
}
return this;
}

/**
* {@inheritDoc}
*/
public int read() throws IOException
{
return in.read();
}

/**
* {@inheritDoc}
*/
public int read(final byte b[]) throws IOException,
NullPointerException
{
return in.read(b,0,b.length);
}

/**
* {@inheritDoc}
*/
public int read(final byte b[],
final int off,
final int len) throws IOException,
NullPointerException
{
return in.read(b,off,len);
}

/**
* {@inheritDoc}
*/
public long skip(final long n) throws IOException
{
return in.skip(n);
}

/**
* {@inheritDoc}
*/
public int available() throws IOException
{
return in.available();
}

/**
* {@inheritDoc}
*/
public void close() throws IOException
{
in.close();
}

/**
* {@inheritDoc}
*/
public synchronized void mark(final int readlimit)
{
in.mark(readlimit);
}

/**
* {@inheritDoc}
*/
public synchronized void reset() throws IOException
{
in.reset();
}

/**
* {@inheritDoc}
*/
public boolean markSupported()
{
return in.markSupported();
}

private final PushbackInputStream in;
private final BOM bom;
private boolean skipped = false;

} // UnicodeBOMInputStream

And you're using it this way:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;

public final class UnicodeBOMInputStreamUsage
{
public static void main(final String[] args) throws Exception
{
FileInputStream fis = new FileInputStream("test/offending_bom.txt");
UnicodeBOMInputStream ubis = new UnicodeBOMInputStream(fis);

System.out.println("detected BOM: " + ubis.getBOM());

System.out.print("Reading the content of the file without skipping the BOM: ");
InputStreamReader isr = new InputStreamReader(ubis);
BufferedReader br = new BufferedReader(isr);

System.out.println(br.readLine());

br.close();
isr.close();
ubis.close();
fis.close();

fis = new FileInputStream("test/offending_bom.txt");
ubis = new UnicodeBOMInputStream(fis);
isr = new InputStreamReader(ubis);
br = new BufferedReader(isr);

ubis.skipBOM();

System.out.print("Reading the content of the file after skipping the BOM: ");
System.out.println(br.readLine());

br.close();
isr.close();
ubis.close();
fis.close();
}

} // UnicodeBOMInputStreamUsage

Reading UTF-8 - BOM marker

In Java, you have to consume manually the UTF8 BOM if present. This behaviour is documented in the Java bug database, here and here. There will be no fix for now because it will break existing tools like JavaDoc or XML parsers. The Apache IO Commons provides a BOMInputStream to handle this situation.

Take a look at this solution: Handle UTF8 file with BOM

Preventing Unicode Byte Order Mark to be written in the middle of a file

the BOM bytes is inserted when you call get UTF-16 with BOM:

final byte[] title = "Title: ".getBytes("UTF-16");

check the title.length and you will find it contains additional 2 bytes for BOM marker

so you could process these arrays and remove the BOM from it before wrapp into ByteBuffer, or you can ignore it when you write ByteBuffer to file

other solution, you can use UTF-16 Little/BIG Endianness which will not write BOM marker:

final byte[] title = "Title: ".getBytes("UTF-16LE"); 

or you can use UTF-8 if UTF-16 is not required:

final byte[] title = "Title: ".getBytes("UTF-8");

What is the Byte Order Mark preceding the magic number of this PDF?

Read this answer on a related topic:

According to the PDF standard (ISO 32000-2, similarly also already in ISO 32000-1):

The PDF file begins with the 5 characters “%PDF–”

(ISO 32000-2, section 7.5.2 "File header")

In particular there is nothing like "UTF-8 encoded PDFs (preceded with the UTF-8 Byte Order Mark)", already that BOM is invalid.

Nonetheless, Adobe Reader and other PDF viewers open files with a few leading arbitrary trash bytes as PDFs without complaint. This happens because Adobe Reader explicitly is lax about the specification

Acrobat viewers require only that the header appear somewhere within the first 1024 bytes of the file.

(Adobe PDF Reference sixth edition, appendix H.3 "Implementation Notes", item 13)

and other PDF viewers follow its lead.

Thus, if you want to use magic numbers to identify file type validity as in "valid according to the specification", you must only accept files beginning with the 5 characters “%PDF-”. On the other hand, if you want to judge validity by "opens in common viewers", you have to accept anything with “%PDF-” appearing somewhere within the first 1024 bytes of the file.

Even worse,

Acrobat viewers also accept a header of the form

%!PS−Adobe−N.n PDF−M.m

(Adobe PDF Reference sixth edition, appendix H.3 "Implementation Notes", item 14)

So in this case you also have to accept this sequence in the first 1024 bytes...


I didn't close your question as duplicate of the referenced answer because you appear to believe that there is something like "UTF-8 encoded PDFs", that some BOMs may be valid in front of the “%PDF-” – No, nothing is allowed in front of those header bytes, neither an UTF BOM nor anything else.

Reading text files in Ada: Get_Line reads the byte-order mark as well

Ada.Text_IO is specified to handle ISO-8859-1 encoded text, so ignoring an UTF-8 feature is the proper thing to do.

If Ada.Wide_Text_IO and Ada.Wide_Wide_Text_IO also output the byte-order-mark, when asked to read UTF-8 encoded text, then you should consider reporting it as a bug to GCC - but as there is quite a lot of implementation defined details for the text I/O packages in Ada, you should be ready for a "wont fix" answer.



Related Topics



Leave a reply



Submit