File: Ajp13OutputStream.java

package info (click to toggle)
jenkins-winstone 0.9.10-jenkins-37+dfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 2,292 kB
  • sloc: java: 14,196; xml: 943; makefile: 110; sh: 11
file content (195 lines) | stat: -rw-r--r-- 8,236 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/*
 * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
 * Distributed under the terms of either:
 * - the common development and distribution license (CDDL), v1.0; or
 * - the GNU Lesser General Public License, v2.1 or later
 */
package winstone.ajp13;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;
import java.util.Map;

import javax.servlet.http.Cookie;

import winstone.Logger;
import winstone.URIUtil;
import winstone.WinstoneException;
import winstone.WinstoneOutputStream;

/**
 * Extends the winstone output stream, so that the ajp13 protocol requirements
 * can be fulfilled.
 * 
 * @author mailto: <a href="rick_knowles@hotmail.com">Rick Knowles</a>
 * @version $Id: Ajp13OutputStream.java,v 1.7 2007/05/05 00:52:50 rickknowles Exp $
 */
public class Ajp13OutputStream extends WinstoneOutputStream {
    // Container originated packet types
    byte CONTAINER_SEND_BODY_CHUNK = 0x03;
    byte CONTAINER_SEND_HEADERS = 0x04;
    byte CONTAINER_END_RESPONSE = 0x05;

    // byte CONTAINER_GET_BODY_CHUNK = 0x06;
    // byte CONTAINER_CPONG_REPLY = 0x09;

    static Map headerCodes = null;

    static {
        headerCodes = new Hashtable();
        headerCodes.put("content-type", new byte[] { (byte) 0xA0, 0x01 });
        headerCodes.put("content-language", new byte[] { (byte) 0xA0, 0x02 });
        headerCodes.put("content-length", new byte[] { (byte) 0xA0, 0x03 });
        headerCodes.put("date", new byte[] { (byte) 0xA0, 0x04 });
        headerCodes.put("last-modified", new byte[] { (byte) 0xA0, 0x05 });
        headerCodes.put("location", new byte[] { (byte) 0xA0, 0x06 });
        headerCodes.put("set-cookie", new byte[] { (byte) 0xA0, 0x07 });
        headerCodes.put("set-cookie2", new byte[] { (byte) 0xA0, 0x08 });
        headerCodes.put("servlet-engine", new byte[] { (byte) 0xA0, 0x09 });
        headerCodes.put("server", new byte[] { (byte) 0xA0, 0x09 });
        headerCodes.put("status", new byte[] { (byte) 0xA0, 0x0A });
        headerCodes.put("www-authenticate", new byte[] { (byte) 0xA0, 0x0B });
    }

    private String headerEncoding;

    public Ajp13OutputStream(OutputStream outStream, String headerEncoding) {
        super(outStream, false);
        this.headerEncoding = headerEncoding;
    }

    public void commit() throws IOException {
        Logger.log(Logger.FULL_DEBUG, Ajp13Listener.AJP_RESOURCES,
                "Ajp13OutputStream.CommittedBytes", "" + this.bytesCommitted);
        this.buffer.flush();

        // If we haven't written the headers yet, write them out
        if (!this.committed) {
            this.owner.validateHeaders();
            this.committed = true;

            ByteArrayOutputStream headerArrayStream = new ByteArrayOutputStream();
            for (Object o1 : this.owner.getHeaders()) {
                String header = (String) o1;
                int colonPos = header.indexOf(':');
                if (colonPos == -1)
                    throw new WinstoneException(Ajp13Listener.AJP_RESOURCES.getString(
                            "Ajp13OutputStream.NoColonHeader", header));
                String headerName = URIUtil.noCRLF(header.substring(0, colonPos).trim());
                String headerValue = URIUtil.noCRLF(header.substring(colonPos + 1).trim());
                byte headerCode[] = (byte[]) headerCodes.get(headerName
                        .toLowerCase());
                if (headerCode == null) {
                    headerArrayStream.write(getStringBlock(headerName));
                } else {
                    headerArrayStream.write(headerCode);
                }
                headerArrayStream.write(getStringBlock(headerValue));
            }

            for (Object o : this.owner.getCookies()) {
                Cookie cookie = (Cookie) o;
                String cookieText = this.owner.writeCookie(cookie);
                int colonPos = cookieText.indexOf(':');
                if (colonPos == -1)
                    throw new WinstoneException(Ajp13Listener.AJP_RESOURCES.getString(
                            "Ajp13OutputStream.NoColonHeader", cookieText));
                String headerName = cookieText.substring(0, colonPos).trim();
                String headerValue = cookieText.substring(colonPos + 1).trim();
                byte headerCode[] = (byte[]) headerCodes.get(headerName.toLowerCase());
                if (headerCode == null) {
                    headerArrayStream.write(getStringBlock(headerName));
                } else {
                    headerArrayStream.write(headerCode);
                }
                headerArrayStream.write(getStringBlock(headerValue));
            }

            // Write packet header + prefix + status code + status msg + header
            // count
            byte headerArray[] = headerArrayStream.toByteArray();
            byte headerPacket[] = new byte[12];
            headerPacket[0] = (byte) 0x41;
            headerPacket[1] = (byte) 0x42;
            setIntBlock(headerArray.length + 8, headerPacket, 2);
            headerPacket[4] = CONTAINER_SEND_HEADERS;
            setIntBlock(this.owner.getStatus(), headerPacket, 5);
            setIntBlock(0, headerPacket, 7); // empty msg
            headerPacket[9] = (byte) 0x00;
            setIntBlock(this.owner.getHeaders().size()
                    + this.owner.getCookies().size(), headerPacket, 10);

            // Ajp13Listener.packetDump(headerPacket, headerPacket.length);
            // Ajp13Listener.packetDump(headerArray, headerArray.length);

            this.outStream.write(headerPacket);
            this.outStream.write(headerArray);
        }

        // Write out the contents of the buffer in max 8k chunks
        byte bufferContents[] = this.buffer.toByteArray();
        int position = 0;
        while (position < bufferContents.length) {
            int packetLength = Math.min(bufferContents.length - position, 8184);
            byte responsePacket[] = new byte[packetLength + 8];
            responsePacket[0] = 0x41;
            responsePacket[1] = 0x42;
            setIntBlock(packetLength + 4, responsePacket, 2);
            responsePacket[4] = CONTAINER_SEND_BODY_CHUNK;
            setIntBlock(packetLength, responsePacket, 5);
            System.arraycopy(bufferContents, position, responsePacket, 7, packetLength);
            responsePacket[packetLength + 7] = 0x00;
            position += packetLength;

            // Ajp13Listener.packetDump(responsePacket, responsePacket.length);
            this.outStream.write(responsePacket);
        }

        this.buffer.reset();
        this.bufferPosition = 0;
    }

    public void finishResponse() throws IOException {
        // Send end response packet
        byte endResponse[] = new byte[] { 0x41, 0x42, 0x00, 0x02,
                CONTAINER_END_RESPONSE, 1 };
        // Ajp13Listener.packetDump(endResponse, endResponse.length);
        this.outStream.write(endResponse);
    }

    /**
     * Useful generic method for getting ajp13 format integers in a packet.
     */
    public byte[] getIntBlock(int integer) {
        byte hi = (byte) (0xFF & (integer >> 8));
        byte lo = (byte) (0xFF & (integer - (hi << 8)));
        return new byte[] { hi, lo };
    }

    /**
     * Useful generic method for setting ajp13 format integers in a packet.
     */
    public static void setIntBlock(int integer, byte packet[], int offset) {
        byte hi = (byte) (0xFF & (integer >> 8));
        byte lo = (byte) (0xFF & (integer - (hi << 8)));
        packet[offset] = hi;
        packet[offset + 1] = lo;
    }

    /**
     * Useful generic method for getting ajp13 format strings in a packet.
     */
    public byte[] getStringBlock(String text)
            throws UnsupportedEncodingException {
        byte textBytes[] = text.getBytes(headerEncoding);
        byte outArray[] = new byte[textBytes.length + 3];
        System.arraycopy(getIntBlock(textBytes.length), 0, outArray, 0, 2);
        System.arraycopy(textBytes, 0, outArray, 2, textBytes.length);
        outArray[textBytes.length + 2] = 0x00;
        return outArray;
    }

}