diff --git a/jme/src/main/java/org/luaj/vm2/libs/jme/JmeIoLib.java b/jme/src/main/java/org/luaj/vm2/libs/jme/JmeIoLib.java index 8c60a940..de6c8501 100644 --- a/jme/src/main/java/org/luaj/vm2/libs/jme/JmeIoLib.java +++ b/jme/src/main/java/org/luaj/vm2/libs/jme/JmeIoLib.java @@ -83,20 +83,15 @@ public class JmeIoLib extends IoLib { } protected File openFile( String filename, boolean readMode, boolean appendMode, boolean updateMode, boolean binaryMode ) throws IOException { + if ( appendMode || updateMode ) { + throw new IOException("unsupported mode"); + } String url = "file:///" + filename; int mode = readMode? Connector.READ: Connector.READ_WRITE; StreamConnection conn = (StreamConnection) Connector.open( url, mode ); File f = readMode? new FileImpl(conn, conn.openInputStream(), null): - new FileImpl(conn, conn.openInputStream(), conn.openOutputStream()); - /* - if ( appendMode ) { - f.seek("end",0); - } else { - if ( ! readMode ) - conn.truncate(0); - } - */ + new FileImpl(conn, null, conn.openOutputStream()); return f; } diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$FileImpl.class b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$FileImpl.class index 736d070c..c6e4f505 100644 Binary files a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$FileImpl.class and b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$FileImpl.class differ diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$StdinFile.class b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$StdinFile.class index de1b21f6..69eff34e 100644 Binary files a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$StdinFile.class and b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$StdinFile.class differ diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$StdoutFile.class b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$StdoutFile.class index 70ecf29d..6f3e1d23 100644 Binary files a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$StdoutFile.class and b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib$StdoutFile.class differ diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.class b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.class index a0999627..53a6d00c 100644 Binary files a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.class and b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.class differ diff --git a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.java b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.java index 4ced4734..cfd5dc5f 100644 --- a/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.java +++ b/jse/src/main/java/org/luaj/vm2/libs/jse/JseIoLib.java @@ -84,14 +84,14 @@ public class JseIoLib extends IoLib { } protected File openFile( String filename, boolean readMode, boolean appendMode, boolean updateMode, boolean binaryMode ) throws IOException { - RandomAccessFile f = new RandomAccessFile(filename,readMode? "r": "rw"); + RandomAccessFile f = new RandomAccessFile(filename, readMode && !updateMode ? "r": "rw"); if ( appendMode ) { f.seek(f.length()); } else { if ( ! readMode ) f.setLength(0); } - return new FileImpl( f ); + return new FileImpl( f, readMode || updateMode, !readMode || updateMode, appendMode ); } protected File openProgram(String prog, String mode) throws IOException { @@ -104,7 +104,7 @@ public class JseIoLib extends IoLib { protected File tmpFile() throws IOException { java.io.File f = java.io.File.createTempFile(".luaj","bin"); f.deleteOnExit(); - return new FileImpl( new RandomAccessFile(f,"rw") ); + return new FileImpl( new RandomAccessFile(f,"rw"), true, true, false ); } private static void notimplemented() { @@ -116,21 +116,27 @@ public class JseIoLib extends IoLib { private final RandomAccessFile file; private final InputStream is; private final OutputStream os; + private final boolean readable; + private final boolean writable; + private final boolean append; private boolean closed = false; private boolean nobuffer = false; - private FileImpl( RandomAccessFile file, InputStream is, OutputStream os ) { + private FileImpl( RandomAccessFile file, InputStream is, OutputStream os, boolean readable, boolean writable, boolean append ) { this.file = file; this.is = is!=null? is.markSupported()? is: new BufferedInputStream(is): null; this.os = os; + this.readable = readable; + this.writable = writable; + this.append = append; } - private FileImpl( RandomAccessFile f ) { - this( f, null, null ); + private FileImpl( RandomAccessFile f, boolean readable, boolean writable, boolean append ) { + this( f, null, null, readable, writable, append ); } private FileImpl( InputStream i ) { - this( null, i, null ); + this( null, i, null, true, false, false ); } private FileImpl( OutputStream o ) { - this( null, null, o ); + this( null, null, o, false, true, false ); } public String tojstring() { return "file (" + (this.closed ? "closed" : String.valueOf(this.hashCode())) + ")"; @@ -149,11 +155,15 @@ public class JseIoLib extends IoLib { os.flush(); } public void write(LuaString s) throws IOException { + if ( ! writable ) + throw new IOException("file is not writable"); if ( os != null ) os.write( s.m_bytes, s.m_offset, s.m_length ); - else if ( file != null ) + else if ( file != null ) { + if ( append ) + file.seek(file.length()); file.write( s.m_bytes, s.m_offset, s.m_length ); - else + } else notimplemented(); if ( nobuffer ) flush(); @@ -181,11 +191,15 @@ public class JseIoLib extends IoLib { // get length remaining to read public int remaining() throws IOException { + if ( ! readable ) + throw new IOException("file is not readable"); return file!=null? (int) (file.length()-file.getFilePointer()): -1; } // peek ahead one character public int peek() throws IOException { + if ( ! readable ) + throw new IOException("file is not readable"); if ( is != null ) { is.mark(1); int c = is.read(); @@ -203,6 +217,8 @@ public class JseIoLib extends IoLib { // return char if read, -1 if eof, throw IOException on other exception public int read() throws IOException { + if ( ! readable ) + throw new IOException("file is not readable"); if ( is != null ) return is.read(); else if ( file != null ) { @@ -214,6 +230,8 @@ public class JseIoLib extends IoLib { // return number of bytes read if positive, -1 if eof, throws IOException public int read(byte[] bytes, int offset, int length) throws IOException { + if ( ! readable ) + throw new IOException("file is not readable"); if (file!=null) { return file.read(bytes, offset, length); } else if (is!=null) { diff --git a/jse/src/test/java/org/luaj/vm2/FragmentsTest.java b/jse/src/test/java/org/luaj/vm2/FragmentsTest.java index c730a143..c491c2cc 100644 --- a/jse/src/test/java/org/luaj/vm2/FragmentsTest.java +++ b/jse/src/test/java/org/luaj/vm2/FragmentsTest.java @@ -21,6 +21,9 @@ ******************************************************************************/ package org.luaj.vm2; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.concurrent.atomic.AtomicReference; @@ -236,6 +239,98 @@ public class FragmentsTest extends TestSuite { runFragment(LuaValue.valueOf(7), "return 1 | 2 & 6\n"); } + public void testIoOpenReadModeDisallowsWrite() throws Exception { + File file = writeTempFile("read-mode", "hello"); + try { + Globals globals = JsePlatform.standardGlobals(); + try { + globals.load("local f = io.open(" + quote(file.getAbsolutePath()) + ", 'r') f:write('x')", "io_r.lua").call(); + fail("expected write on read-only file to fail"); + } catch (LuaError e) { + assertTrue(e.getMessage().indexOf("not writable") >= 0); + } + } finally { + file.delete(); + } + } + + public void testIoOpenWriteModeDisallowsRead() throws Exception { + File file = File.createTempFile("luaj-io", ".txt"); + try { + Globals globals = JsePlatform.standardGlobals(); + try { + globals.load("local f = io.open(" + quote(file.getAbsolutePath()) + ", 'w') return f:read('*a')", "io_w.lua").call(); + fail("expected read on write-only file to fail"); + } catch (LuaError e) { + assertTrue(e.getMessage().indexOf("not readable") >= 0); + } + } finally { + file.delete(); + } + } + + public void testIoOpenUpdateModesSupportReadWrite() throws Exception { + File file = writeTempFile("update-mode", "abc"); + try { + Globals globals = JsePlatform.standardGlobals(); + Varargs result = globals.load( + "local f = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'r+'))\n" + + "local before = f:read('*a')\n" + + "f:seek('set', 0)\n" + + "f:write('xyz')\n" + + "f:seek('set', 0)\n" + + "local after = f:read('*a')\n" + + "f:close()\n" + + "return before, after\n", + "io_rplus.lua").invoke(); + assertEquals("abc", result.arg1().tojstring()); + assertEquals("xyz", result.arg(2).tojstring()); + } finally { + file.delete(); + } + } + + public void testIoOpenAppendPreservesExistingContent() throws Exception { + File file = writeTempFile("append-mode", "abc"); + try { + Globals globals = JsePlatform.standardGlobals(); + LuaValue result = globals.load( + "local f = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'a'))\n" + + "f:write('xyz')\n" + + "f:close()\n" + + "local g = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'r'))\n" + + "local contents = g:read('*a')\n" + + "g:close()\n" + + "return contents\n", + "io_a.lua").call(); + assertEquals("abcxyz", result.tojstring()); + } finally { + file.delete(); + } + } + + public void testIoOpenAppendUpdateAppendsOnWriteAfterSeek() throws Exception { + File file = writeTempFile("append-update", "abc"); + try { + Globals globals = JsePlatform.standardGlobals(); + Varargs result = globals.load( + "local f = assert(io.open(" + quote(file.getAbsolutePath()) + ", 'a+'))\n" + + "f:seek('set', 0)\n" + + "local before = f:read('*a')\n" + + "f:seek('set', 0)\n" + + "f:write('xyz')\n" + + "f:seek('set', 0)\n" + + "local after = f:read('*a')\n" + + "f:close()\n" + + "return before, after\n", + "io_aplus.lua").invoke(); + assertEquals("abc", result.arg1().tojstring()); + assertEquals("abcxyz", result.arg(2).tojstring()); + } finally { + file.delete(); + } + } + public void testTableMove() { runFragment( LuaValue.varargsOf(new LuaValue[] { @@ -844,5 +939,20 @@ public class FragmentsTest extends TestSuite { + "return v1, v2, v3"); } + + private static File writeTempFile(String prefix, String contents) throws IOException { + File file = File.createTempFile(prefix, ".txt"); + FileOutputStream out = new FileOutputStream(file); + try { + out.write(contents.getBytes("UTF-8")); + } finally { + out.close(); + } + return file; + } + + private static String quote(String value) { + return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'"; + } } }