blob: d923f6f53afec7de0a34e1f74c8fc366f5b123f4 [file] [log] [blame]
//
// ========================================================================
// 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);
}
}