1 /** 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * SPDX-License-Identifier: Apache-2.0. 4 */ 5 6 package software.amazon.awssdk.crt.http; 7 8 import java.nio.ByteBuffer; 9 import java.nio.charset.Charset; 10 import java.nio.charset.StandardCharsets; 11 import java.util.ArrayList; 12 import java.util.List; 13 14 /** 15 * A wrapper class for http header key-value pairs 16 */ 17 public class HttpHeader { 18 private final static int BUFFER_INT_SIZE = 4; 19 private final static Charset UTF8 = StandardCharsets.UTF_8; 20 private byte[] name; /* Not final, Native will manually set name after calling empty Constructor. */ 21 private byte[] value; /* Not final, Native will manually set value after calling empty Constructor. */ 22 23 /** Called by Native to create a new HttpHeader. This is so that Native doesn't have to worry about UTF8 24 * encoding/decoding issues. The user thread will deal with them when they call getName() or getValue() **/ HttpHeader()25 private HttpHeader() {} 26 27 /** 28 * 29 * @param name header name 30 * @param value header value 31 */ HttpHeader(String name, String value)32 public HttpHeader(String name, String value){ 33 this.name = name.getBytes(UTF8); 34 this.value = value.getBytes(UTF8); 35 } 36 37 /** 38 * 39 * @param name header name 40 * @param value header value 41 */ HttpHeader(byte[] name, byte[] value)42 public HttpHeader(byte[] name, byte[] value){ 43 this.name = name; 44 this.value = value; 45 } 46 47 /** 48 * 49 * @return the name of the header, converted to a UTF-8 string 50 */ getName()51 public String getName() { 52 if (name == null) { 53 return ""; 54 } 55 return new String(name, UTF8); 56 } 57 58 /** 59 * 60 * @return the name of the header, in raw bytes 61 */ getNameBytes()62 public byte[] getNameBytes() { 63 return name; 64 } 65 66 /** 67 * 68 * @return the value of the header, converted to a UTF-8 string 69 */ getValue()70 public String getValue() { 71 if (value == null) { 72 return ""; 73 } 74 return new String(value, UTF8); 75 } 76 77 /** 78 * 79 * @return the value of the header, in raw bytes 80 */ getValueBytes()81 public byte[] getValueBytes() { 82 return value; 83 } 84 85 @Override toString()86 public String toString() { 87 return getName() + ":" + getValue(); 88 } 89 90 /** Each header is marshalled as 91 * [4-bytes BE name length] [variable length name value] [4-bytes BE value length] [variable length value value] 92 * @param headersBlob Blob of encoded headers 93 * @return array of decoded headers 94 */ loadHeadersListFromMarshalledHeadersBlob(ByteBuffer headersBlob)95 public static List<HttpHeader> loadHeadersListFromMarshalledHeadersBlob(ByteBuffer headersBlob) { 96 List<HttpHeader> headers = new ArrayList<>(16); 97 98 while(headersBlob.hasRemaining()) { 99 int nameLen = headersBlob.getInt(); 100 101 // we want to protect against 0 length header names, 0 length values are fine. 102 // the marshalling layer will make sure that even if a length is 0, the 0 will 103 // still be stored in the byte array. 104 if (nameLen > 0) { 105 byte[] nameBuf = new byte[nameLen]; 106 headersBlob.get(nameBuf); 107 int valLen = headersBlob.getInt(); 108 byte[] valueBuf = new byte[valLen]; 109 headersBlob.get(valueBuf); 110 headers.add(new HttpHeader(nameBuf, valueBuf)); 111 } 112 } 113 114 return headers; 115 } 116 117 /** 118 * Lists of headers are marshalled as follows: 119 * 120 * each string field is: [4-bytes BE] [variable length bytes specified by the 121 * previous field] 122 * 123 * @param headers List of header name-value pairs 124 * 125 * @return encoded blob of headers 126 */ marshalHeadersForJni(List<HttpHeader> headers)127 public static byte[] marshalHeadersForJni(List<HttpHeader> headers) { 128 int size = 0; 129 130 for (HttpHeader header : headers) { 131 if (header.getNameBytes().length > 0) { 132 size += header.getNameBytes().length + header.getValueBytes().length + (BUFFER_INT_SIZE * 2); 133 } 134 } 135 136 ByteBuffer buffer = ByteBuffer.allocate(size); 137 for (HttpHeader header : headers) { 138 if (header.getNameBytes().length > 0) { 139 buffer.putInt(header.getNameBytes().length); 140 buffer.put(header.getNameBytes()); 141 buffer.putInt(header.getValueBytes().length); 142 buffer.put(header.getValueBytes()); 143 } 144 } 145 146 return buffer.array(); 147 } 148 149 150 /** 151 * @param headersBlob encoded headers blob 152 * @return array of headers 153 * @see #loadHeadersListFromMarshalledHeadersBlob 154 */ loadHeadersFromMarshalledHeadersBlob(ByteBuffer headersBlob)155 public static HttpHeader[] loadHeadersFromMarshalledHeadersBlob(ByteBuffer headersBlob) { 156 List<HttpHeader> headers = loadHeadersListFromMarshalledHeadersBlob(headersBlob); 157 HttpHeader[] headersArray = new HttpHeader[headers.size()]; 158 return headers.toArray(headersArray); 159 } 160 } 161