https://bz.apache.org/bugzilla/show_bug.cgi?id=61282

            Bug ID: 61282
           Summary: org.apache.catalina.connector.CoyoteInputStream
                    asynchronous read
           Product: Tomcat 9
           Version: 9.0.0.M22
          Hardware: PC
                OS: Linux
            Status: NEW
          Severity: normal
          Priority: P2
         Component: Catalina
          Assignee: dev@tomcat.apache.org
          Reporter: 94544...@qq.com
  Target Milestone: -----

the byte array
byte[] ab=new byte[4096];
int len=is.read(ab,0,ab.length);

After onDataAvailable returns to container, the content of ab is changed if
len==4096, the content of ab is not changed if len!=4096.


The following is the java class I used to do the test. I issue is explained in
the onComplete method.
===========================


package zede.consult.webapp;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import zede.util.Util;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.json.Json;
import javax.json.stream.JsonGenerator;
import javax.servlet.AsyncListener;

public class BAtest extends HttpServlet {

    static String dirUpload;
    public static String dirTmp, dirTarget, dirTargetPost;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse
response)
            throws ServletException, IOException {

        StringWriter sw = new StringWriter();
        try {
            JsonGenerator g = Json.createGenerator(sw); //out
            g.writeStartObject().write("idReq", 0);
            if (WebApp.writeReason(g, null)) { //no more information
            }
            g.writeEnd();
            g.flush();
        } catch (Throwable t) {
            if (WebApp.debug) {
                t.printStackTrace(WebApp.out);
                WebApp.out.flush();
            }
            try {
                sw = new StringWriter();
                JsonGenerator g = Json.createGenerator(sw); //out
                g.writeStartObject().write("idReq", 0);
                if (WebApp.writeReason(g, null)) { //no more information
                }
                g.writeEnd();
                g.flush();
            } catch (Throwable t2) {
                return;
            }
        }
        try {
            WebApp.sendRes_json(sw.toString(), response);
            //os.close(); we do not open it.
            //PrintWriter out=response.getWriter();
            //out.print(json);out.flush();
        } catch (Throwable ex) {
            //just swallow it
//Logger.getLogger(BA.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse
response)
            throws ServletException, IOException {
        AsyncContext ac = request.startAsync();
        ACHandler_Put h = new ACHandler_Put(ac, request, response);
        ac.addListener(h);
        ac.start(h);
    }

    class ACHandler_Put implements ReadListener, AsyncListener, Runnable {

        ServletInputStream is;
        int bytesReceived, bytesSaved; //this is different from ut.saved which
is got from harddisk, this is instructed to save.
        int saved, size; //int for length is more than enough.
        ConcurrentLinkedQueue<DataPiece> q2save = new
ConcurrentLinkedQueue<>();
        public final AsyncContext ac;
        public final HttpServletRequest request;
        public final HttpServletResponse response;
        public int idReq;
        public Ex_Coded4Web exweb;

        public ACHandler_Put(AsyncContext ac, HttpServletRequest request,
HttpServletResponse response) {
            this.ac = ac;
            this.request = request;
            this.response = response;
        }

        @Override
        public void run() {
            try {
                String slength = request.getHeader("Content-Length");
                if (slength == null) {
                    //in this case, because the fault of the client's agent, we
should reject it,
                    //but since we are nice, so we accept it, but the length is
the length specified in the UpLoadTask
                } else {
                    try {
                        size = Integer.parseInt(slength);
                    } catch (Throwable t) {
                        throw new
Ex_Coded4Web(WebApp.Reason_ContentLengthParseFailed, "Content_Length:" +
slength + " can not be converted into int");
                    }
                }
                startRead();
                return; //we do not call complete at this time point
            } catch (Ex_Coded4Web e) {
                exweb = e;
            } catch (Throwable t) {
                t.printStackTrace();
                exweb = new Ex_Coded4Web(WebApp.Reason_UncaughtException,
t.getMessage());
            }
            complete();
        }

        void startRead() {
            File f = new File("stream.out");
//            System.out.println(f.getAbsolutePath());
//tomcat home or catalina home
            f = new File("/home/jack/Pictures/stream.out");
            try {
                fos = new FileOutputStream(f);
            } catch (FileNotFoundException ex) {
            }

            bytesReceived = bytesSaved = saved;
            try {
                is = request.getInputStream();
//System.out.println(is.getClass().getName());
//org.apache.catalina.connector.CoyoteInputStream
            } catch (IOException ex) {
                exweb = new Ex_Coded4Web(WebApp.Reason_FailedOnReceiving,
"unable get uploading stream:" + ex.getMessage());
                complete();
                return;
            }
            //is=java.util.Base64.getDecoder().wrap(is);
            is.setReadListener(ACHandler_Put.this);
        }

        @Override
        public void onDataAvailable() {
            {
                int len; //we have to check the bytes saved incase someone
attack us.
                byte[] ab = null;//q4096.poll();
                if (ab == null) {
                    ab = new byte[4096];
                }
                String msg;
                while (true) {
                    try {
                        if (!is.isReady()) { //this can throw the same
EOFException as read.
                            break;
                        }
                        len = is.read(ab, 0, ab.length);
                    } catch (IOException ex) { //read exception
                        //this happens usually as a result of user refresh the
webpage before uploading completes.
                        ex.printStackTrace();
                        msg = "isread/read:" + ex.getMessage();
                        exweb = new
Ex_Coded4Web(WebApp.Reason_FailedOnReceiving, msg);
                        complete();
                        return;
                    }
                    if (-1 == len) {
                        //this will not happen, or I did not see it happen
                        //onAllDataRead will be called.
                        //if this happens, then this should have the same
effect as onAllDataRead()
                        if (WebApp.debug) {
                            System.out.println("put len=-1");

                        }
                        return;
                    }
//                if(len==0){ //will never happen, otherwise isready is useless
//                    break;
//                }
                    DataPiece dp = new DataPiece(ab, bytesReceived, len); //new
DataPiece(ByteBuffer.wrap(ab, 0, len), bytesReceived);
                    bytesReceived += len;
                    if (bytesReceived > size) {
                        msg = "size " + bytesReceived + " is exceeding the
inited length:" + size;
                        System.out.println(msg);
                        exweb = new
Ex_Coded4Web(WebApp.Reason_SizeExceedDeclaredLength, msg);
                        try {
                            is.close();
                        } catch (Throwable t) { //exweb !=null, so just swallow
                        }
                        complete();
                        return;
                    }
                    //System.out.println("read data +" + len + "=" +
bytesReceived + "/" + ut.size + " saved:" + bytesSaved);
                    //I would do this in asynchronous mode, but to show the
data is correctly received,
                    //so we save it with an outputstream, and we can compre the
save file and original file
                    //they are same.
                    if (fos != null) {
                        try {
                            fos.write(ab, 0, len);
                        } catch (IOException ex) {
                        }
                    }
                    q2save.offer(dp);
                }
            }
        }
        FileOutputStream fos;
        MappedByteBuffer bb = null;
        byte[] bbO;
        int offset;

        /**
         * In the current servlet specification, I do not know whether read
         * returns -1 will happen or not. Since this notification should has
the
         * same effect as read returns -1.
         *
         */
        @Override
        public void onAllDataRead() {
            if (WebApp.debug) {
                System.out.println("BA.put onAllDataRead, bytesReceived:" +
bytesReceived + ", size:" + size);
            }
            try {
                //save();
                if (bytesReceived < size) {
                    exweb = new
Ex_Coded4Web(WebApp.Reason_SizeLessThanDeclaredLength, "size " + bytesReceived
+ " is less than the inited length:" + size);
                }
            } catch (Throwable t) {
                if (WebApp.debug) {
                    t.printStackTrace();

                }
            }
            if (fos != null) {
                try {
                    fos.flush();
                    fos.close();
                } catch (IOException ex) {
                }
            }
            complete();
        }

        //of reading from network
        @Override
        public void onError(Throwable t) {
            try {
                //one case, when uploading is not finished, browser crashed, or
refreshed, so the uploading is canceled.
                System.out.println("onError when receiving data from network");
                t.printStackTrace();

                exweb = new Ex_Coded4Web(WebApp.Reason_FailedOnReceiving,
"onError when receiving:" + t.getMessage());
                //we do not flush here, since q2save might not be empty yet
                //we just call save instead
                complete(); //but why onComplete is not called as expected??
            } catch (Throwable t2) {
                System.out.println("unexpected inside onError");
                t2.printStackTrace();

            }
        }

        //servlet asyncContext complete, onError will also call this?
        /**
         * we have two copies of received data: DataPiece.ab and DataPiece.ab2,
         * we compare them, and we compare them to the original data.
         * 
         * we can find that the copy returned by ServletInputStream.read is
changed
         * for the len=4096, if the len is not 4096, they are not changed.
         * 
         * 
         * @param event 
         */
        @Override
        public void onComplete(AsyncEvent event) {
            try { //this is the original file
                File f = new File("/home/jack/Pictures/a.pdf");
                FileChannel fc = new FileInputStream(f).getChannel();
                bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
                /*
                if (bb.hasArray()) {
                    System.out.println("hasArray:yes!");
                    bbO = bb.array();
                    offset = bb.arrayOffset();
                } else {
                    System.out.println("hasArray:No!");
                }
                */
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            Iterator<DataPiece> I = q2save.iterator();
            while (I.hasNext()) {
                DataPiece dp = I.next();
                bb.position(dp.offset);
                ByteBuffer bb1 = ByteBuffer.wrap(dp.ab, 0, dp.len); //or dp.ab2
                boolean e1 = Util.equals(bb1, bb);
                String msg = "bytesSaved:" + bytesSaved + "+" + dp.len + "
bb1:" + e1;
                bb.position(dp.offset);
                ByteBuffer bb2 = ByteBuffer.wrap(dp.ab2, 0, dp.len); //or dp.ab
                msg += " bb2:" + Util.equals(bb2, bb) + " ab:ab2" +
Util.equals(dp.ab, 0, dp.len, dp.ab2, 0);
                System.out.println(msg);
/*
 * 
I got 9 of bb1:true and 242 bb1:false,
     251 of bb2:true and 0 bb2:false
     9 of ab:ab2true and 242 ab:ab2false
 *   if I change the bb1 & bb2, then the result reversed.
 */                
            }
        }

        @Override
        public void onTimeout(AsyncEvent event) throws IOException {
        }

        @Override
        public void onError(AsyncEvent event) throws IOException {
        }

        @Override
        public void onStartAsync(AsyncEvent event) throws IOException {
        }

        protected void complete() {
            StringWriter sw = new StringWriter();
            try {
                JsonGenerator g = Json.createGenerator(sw); //out
                g.writeStartObject().write("idReq", idReq);
                if (WebApp.writeReason(g, exweb)) { //no more information
                }
                g.writeEnd();
                g.flush();
            } catch (Throwable t) {
                try {
                    sw = new StringWriter();
                    JsonGenerator g = Json.createGenerator(sw); //out
                    g.writeStartObject().write("idReq", idReq);
                    if (WebApp.writeReason(g, exweb)) { //no more information
                    }
                    g.writeEnd();
                    g.flush();
                } catch (Throwable t2) {
                    return;
                }
            }
            try {
                WebApp.sendRes_json(sw.toString(), response);
                //os.close(); we do not open it.
                //PrintWriter out=response.getWriter();
                //out.print(json);out.flush();
            } catch (Throwable ex) {
                //just swallow it
//Logger.getLogger(BA.class.getName()).log(Level.SEVERE, null, ex);
            }
            ac.complete();
        }

    }

    static class DataPiece {

        //ByteBuffer bb;
        int offset, len;
        byte[] ab, ab2;

        DataPiece(byte[] ab, int offset, int len) {
            this.ab = ab;
            this.offset = offset;
            this.len = len;
            ab2 = new byte[len];
            System.arraycopy(ab, 0, ab2, 0, len);
            //bb = ByteBuffer.wrap(ab2, 0, len);
        }
    }
}

-- 
You are receiving this mail because:
You are the assignee for the bug.
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to