// // ======================================================================== // Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // // You may elect to redistribute this code under either of these licenses. // ======================================================================== // package org.eclipse.jetty.fcgi.generator; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; public class ClientGenerator extends Generator { // To keep the algorithm simple, and given that the max length of a // frame is 0xFF_FF we allow the max length of a name (or value) to be // 0x7F_FF - 4 (the 4 is to make room for the name (or value) length). public static final int MAX_PARAM_LENGTH = 0x7F_FF - 4; public ClientGenerator(ByteBufferPool byteBufferPool) { super(byteBufferPool); } public Result generateRequestHeaders(int request, HttpFields fields, Callback callback) { request &= 0xFF_FF; final Charset utf8 = StandardCharsets.UTF_8; List<byte[]> bytes = new ArrayList<>(fields.size() * 2); int fieldsLength = 0; for (HttpField field : fields) { String name = field.getName(); byte[] nameBytes = name.getBytes(utf8); if (nameBytes.length > MAX_PARAM_LENGTH) throw new IllegalArgumentException("Field name " + name + " exceeds max length " + MAX_PARAM_LENGTH); bytes.add(nameBytes); String value = field.getValue(); byte[] valueBytes = value.getBytes(utf8); if (valueBytes.length > MAX_PARAM_LENGTH) throw new IllegalArgumentException("Field value " + value + " exceeds max length " + MAX_PARAM_LENGTH); bytes.add(valueBytes); int nameLength = nameBytes.length; fieldsLength += bytesForLength(nameLength); int valueLength = valueBytes.length; fieldsLength += bytesForLength(valueLength); fieldsLength += nameLength; fieldsLength += valueLength; } // Worst case FCGI_PARAMS frame: long name + long value - both of MAX_PARAM_LENGTH int maxCapacity = 4 + 4 + 2 * MAX_PARAM_LENGTH; // One FCGI_BEGIN_REQUEST + N FCGI_PARAMS + one last FCGI_PARAMS ByteBuffer beginRequestBuffer = byteBufferPool.acquire(16, false); BufferUtil.clearToFill(beginRequestBuffer); Result result = new Result(byteBufferPool, callback); result = result.append(beginRequestBuffer, true); // Generate the FCGI_BEGIN_REQUEST frame beginRequestBuffer.putInt(0x01_01_00_00 + request); beginRequestBuffer.putInt(0x00_08_00_00); // Hardcode RESPONDER role and KEEP_ALIVE flag beginRequestBuffer.putLong(0x00_01_01_00_00_00_00_00L); BufferUtil.flipToFlush(beginRequestBuffer, 0); int index = 0; while (fieldsLength > 0) { int capacity = 8 + Math.min(maxCapacity, fieldsLength); ByteBuffer buffer = byteBufferPool.acquire(capacity, true); BufferUtil.clearToFill(buffer); result = result.append(buffer, true); // Generate the FCGI_PARAMS frame buffer.putInt(0x01_04_00_00 + request); buffer.putShort((short)0); buffer.putShort((short)0); capacity -= 8; int length = 0; while (index < bytes.size()) { byte[] nameBytes = bytes.get(index); int nameLength = nameBytes.length; byte[] valueBytes = bytes.get(index + 1); int valueLength = valueBytes.length; int required = bytesForLength(nameLength) + bytesForLength(valueLength) + nameLength + valueLength; if (required > capacity) break; putParamLength(buffer, nameLength); putParamLength(buffer, valueLength); buffer.put(nameBytes); buffer.put(valueBytes); length += required; fieldsLength -= required; capacity -= required; index += 2; } buffer.putShort(4, (short)length); BufferUtil.flipToFlush(buffer, 0); } ByteBuffer lastParamsBuffer = byteBufferPool.acquire(8, false); BufferUtil.clearToFill(lastParamsBuffer); result = result.append(lastParamsBuffer, true); // Generate the last FCGI_PARAMS frame lastParamsBuffer.putInt(0x01_04_00_00 + request); lastParamsBuffer.putInt(0x00_00_00_00); BufferUtil.flipToFlush(lastParamsBuffer, 0); return result; } private int putParamLength(ByteBuffer buffer, int length) { int result = bytesForLength(length); if (result == 4) buffer.putInt(length | 0x80_00_00_00); else buffer.put((byte)length); return result; } private int bytesForLength(int length) { return length > 127 ? 4 : 1; } public Result generateRequestContent(int request, ByteBuffer content, boolean lastContent, Callback callback) { return generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDIN); } }