blob: f98b4c900d052e5fe22f9b832e9077874aa15369 [file] [log] [blame]
// /*
// * Licensed to the Apache Software Foundation (ASF) under one or more
// * contributor license agreements. See the NOTICE file distributed with
// * this work for additional information regarding copyright ownership.
// * The ASF licenses this file to You under the Apache License, Version 2.0
// * (the "License"); you may not use this file except in compliance with
// * the License. You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
using System;
using System.Collections;
using System.IO;
using System.Text;
namespace Apache.NMS.Stomp.Protocol
{
public class StompFrame
{
/// Used to terminate a header line or end of a headers section of the Frame.
public const String NEWLINE = "\n";
/// Used to seperate the Key / Value pairing in Frame Headers
public const String SEPARATOR = ":";
/// Used to mark the End of the Frame.
public const byte FRAME_TERMINUS = (byte) 0;
/// Used to denote a Special KeepAlive command that consists of a single newline.
public const String KEEPALIVE = "KEEPALIVE";
public const byte BREAK = (byte)('\n');
public const byte COLON = (byte)(':');
public const byte ESCAPE = (byte)('\\');
public readonly byte[] ESCAPE_ESCAPE_SEQ = new byte[2]{ 92, 92 };
public readonly byte[] COLON_ESCAPE_SEQ = new byte[2]{ 92, 99 };
public readonly byte[] NEWLINE_ESCAPE_SEQ = new byte[2]{ 92, 110 };
private string command;
private IDictionary properties = new Hashtable();
private byte[] content;
private bool encodingEnabled;
private readonly Encoding encoding = new UTF8Encoding();
public StompFrame()
{
}
public StompFrame(bool encodingEnabled)
{
this.encodingEnabled = encodingEnabled;
}
public StompFrame(string command)
{
this.command = command;
}
public StompFrame(string command, bool encodingEnabled)
{
this.command = command;
this.encodingEnabled = encodingEnabled;
}
public bool EncodingEnabled
{
get { return this.encodingEnabled; }
set { this.encodingEnabled = value; }
}
public byte[] Content
{
get { return this.content; }
set { this.content = value; }
}
public string Command
{
get { return this.command; }
set { this.command = value; }
}
public IDictionary Properties
{
get { return this.properties; }
set { this.properties = value; }
}
public bool HasProperty(string name)
{
return this.properties.Contains(name);
}
public void SetProperty(string name, Object value)
{
if(value == null)
{
return;
}
this.Properties[name] = value.ToString();
}
public string GetProperty(string name)
{
return GetProperty(name, null);
}
public string GetProperty(string name, string fallback)
{
if(this.properties.Contains(name))
{
return this.properties[name] as string;
}
return fallback;
}
public string RemoveProperty(string name)
{
string result = null;
if(this.properties.Contains(name))
{
result = this.properties[name] as string;
this.properties.Remove(name);
}
return result;
}
public void ClearProperties()
{
this.properties.Clear();
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append(GetType().Name + "[ ");
builder.Append("Command=" + Command);
builder.Append(", Properties={");
foreach(string key in this.properties.Keys)
{
builder.Append(" " + key + "=" + this.properties[key]);
}
builder.Append("}, ");
builder.Append("Content=" + this.content ?? this.content.ToString());
builder.Append("]");
return builder.ToString();
}
public void ToStream(BinaryWriter dataOut)
{
if(this.Command == KEEPALIVE)
{
dataOut.Write(BREAK);
dataOut.Flush();
return;
}
StringBuilder builder = new StringBuilder();
builder.Append(this.Command);
builder.Append(NEWLINE);
foreach(String key in this.Properties.Keys)
{
builder.Append(key);
builder.Append(SEPARATOR);
builder.Append(EncodeHeader(this.Properties[key] as string));
builder.Append(NEWLINE);
}
builder.Append(NEWLINE);
dataOut.Write(this.encoding.GetBytes(builder.ToString()));
if(this.Content != null)
{
dataOut.Write(this.Content);
}
dataOut.Write(FRAME_TERMINUS);
}
public void FromStream(BinaryReader dataIn)
{
this.ReadCommandHeader(dataIn);
if(this.command != KEEPALIVE)
{
this.ReadHeaders(dataIn);
this.ReadContent(dataIn);
}
}
private void ReadCommandHeader(BinaryReader dataIn)
{
this.command = ReadLine(dataIn);
if(String.IsNullOrEmpty(this.command))
{
this.command = "KEEPALIVE";
}
}
private void ReadHeaders(BinaryReader dataIn)
{
string line;
while((line = ReadLine(dataIn)) != "")
{
int idx = line.IndexOf(':');
if(idx > 0)
{
string key = line.Substring(0, idx);
string value = line.Substring(idx + 1);
// Stomp v1.1+ allows multiple copies of a property, the first
// one is considered to be the newest, we could figure out how
// to store them all but for now we just throw the rest out.
if(!this.properties.Contains(key))
{
this.properties[key] = DecodeHeader(value);
}
}
else
{
Tracer.Debug("StompFrame - Read Malformed Header: " + line);
}
}
}
private void ReadContent(BinaryReader dataIn)
{
if(this.properties.Contains("content-length"))
{
int size = Int32.Parse(this.properties["content-length"] as string);
this.content = dataIn.ReadBytes(size);
// Read the terminating NULL byte for this frame.
if(dataIn.Read() != 0)
{
Tracer.Debug("StompFrame - Error Invalid Frame, no trailing Null.");
}
}
else
{
MemoryStream ms = new MemoryStream();
int nextChar;
while((nextChar = dataIn.ReadByte()) != 0)
{
// The first Null in this case marks the end of data.
if(nextChar < 0)
{
break;
}
ms.WriteByte((byte)nextChar);
}
this.content = ms.ToArray();
}
}
private String ReadLine(BinaryReader dataIn)
{
MemoryStream ms = new MemoryStream();
while(true)
{
int nextChar = dataIn.Read();
if(nextChar < 0)
{
throw new IOException("Peer closed the stream.");
}
if(nextChar == 10)
{
break;
}
ms.WriteByte((byte)nextChar);
}
byte[] data = ms.ToArray();
return encoding.GetString(data, 0, data.Length);
}
private String EncodeHeader(String header)
{
String result = header;
if(this.encodingEnabled)
{
byte[] utf8buf = this.encoding.GetBytes(header);
MemoryStream stream = new MemoryStream(utf8buf.Length);
foreach(byte val in utf8buf)
{
switch(val)
{
case ESCAPE:
stream.Write(ESCAPE_ESCAPE_SEQ, 0, ESCAPE_ESCAPE_SEQ.Length);
break;
case BREAK:
stream.Write(NEWLINE_ESCAPE_SEQ, 0, NEWLINE_ESCAPE_SEQ.Length);
break;
case COLON:
stream.Write(COLON_ESCAPE_SEQ, 0, COLON_ESCAPE_SEQ.Length);
break;
default:
stream.WriteByte(val);
break;
}
}
byte[] data = stream.ToArray();
result = encoding.GetString(data, 0, data.Length);
}
return result;
}
private String DecodeHeader(String header)
{
MemoryStream decoded = new MemoryStream();
int value = -1;
byte[] utf8buf = this.encoding.GetBytes(header);
MemoryStream stream = new MemoryStream(utf8buf);
while((value = stream.ReadByte()) != -1)
{
if(value == 92)
{
int next = stream.ReadByte();
if (next != -1)
{
switch(next) {
case 110:
decoded.WriteByte(BREAK);
break;
case 99:
decoded.WriteByte(COLON);
break;
case 92:
decoded.WriteByte(ESCAPE);
break;
default:
stream.Seek(-1, SeekOrigin.Current);
decoded.WriteByte((byte)value);
break;
}
}
else
{
decoded.WriteByte((byte)value);
}
}
else
{
decoded.WriteByte((byte)value);
}
}
byte[] data = decoded.ToArray();
return encoding.GetString(data, 0, data.Length);
}
}
}