/* * 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. */ package org.apache.activemq.artemis.core.protocol.stomp.v12; import java.util.concurrent.ScheduledExecutorService; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ICoreMessage; import org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompException; import org.apache.activemq.artemis.core.protocol.stomp.Stomp; import org.apache.activemq.artemis.core.protocol.stomp.StompConnection; import org.apache.activemq.artemis.core.protocol.stomp.StompDecoder; import org.apache.activemq.artemis.core.protocol.stomp.StompFrame; import org.apache.activemq.artemis.core.protocol.stomp.StompSubscription; import org.apache.activemq.artemis.core.protocol.stomp.v11.StompFrameHandlerV11; import org.apache.activemq.artemis.core.protocol.stomp.v11.StompFrameV11; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.utils.ExecutorFactory; import static org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompProtocolMessageBundle.BUNDLE; public class StompFrameHandlerV12 extends StompFrameHandlerV11 { public StompFrameHandlerV12(StompConnection connection, ScheduledExecutorService scheduledExecutorService, ExecutorFactory factory) { super(connection, scheduledExecutorService, factory); decoder = new StompDecoderV12(this); decoder.init(); } @Override public StompFrame createStompFrame(String command) { return new StompFrameV12(command); } @Override public StompFrame createMessageFrame(ICoreMessage serverMessage, ActiveMQBuffer bodyBuffer, StompSubscription subscription, int deliveryCount) throws Exception { StompFrame frame = super.createMessageFrame(serverMessage, bodyBuffer, subscription, deliveryCount); if (!subscription.getAck().equals(Stomp.Headers.Subscribe.AckModeValues.AUTO)) { frame.addHeader(Stomp.Headers.Message.ACK, String.valueOf(serverMessage.getMessageID())); } return frame; } /** * Version 1.2's ACK frame only requires 'id' header * here we use id = messageID */ @Override public StompFrame onAck(StompFrame request) { StompFrame response = null; String messageID = request.getHeader(Stomp.Headers.Ack.ID); String txID = request.getHeader(Stomp.Headers.TRANSACTION); if (txID != null) { ActiveMQServerLogger.LOGGER.stompTXAckNorSupported(); } if (messageID == null) { ActiveMQStompException error = BUNDLE.noIDInAck().setHandler(connection.getFrameHandler()); return error.getFrame(); } try { connection.acknowledge(messageID, null); } catch (ActiveMQStompException e) { response = e.getFrame(); } return response; } protected class StompDecoderV12 extends StompDecoderV11 { protected boolean nextEOLChar = false; public StompDecoderV12(StompFrameHandlerV12 handler) { super(handler); //1.2 allows '\r\n' eolLen = 2; } @Override public void init() { super.init(); nextEOLChar = false; } @Override protected void checkEol() throws ActiveMQStompException { //either \n or \r\n if (workingBuffer[pos - 2] == NEW_LINE) { pos--; } else if (workingBuffer[pos - 2] != CR) { throwInvalid(); } else if (workingBuffer[pos - 1] != NEW_LINE) { throwInvalid(); } } @Override public void init(StompDecoder decoder) { this.data = decoder.data; this.workingBuffer = decoder.workingBuffer; this.pos = decoder.pos; this.command = decoder.command; } @Override protected boolean parseHeaders() throws ActiveMQStompException { outer: while (true) { byte b = workingBuffer[pos++]; switch (b) { //escaping case ESC_CHAR: { if (isEscaping) { //this is a backslash holder.append(b); isEscaping = false; } else { //begin escaping isEscaping = true; } break; } case HEADER_SEPARATOR: { if (inHeaderName) { headerName = holder.getString(); holder.reset(); inHeaderName = false; headerValueWhitespace = true; } whiteSpaceOnly = false; break; } case LN: { if (isEscaping) { holder.append(NEW_LINE); isEscaping = false; } else { holder.append(b); } break; } case RT: { if (isEscaping) { holder.append(CR); isEscaping = false; } else { holder.append(b); } break; } case CR: { if (nextEOLChar) { throw BUNDLE.invalidTwoCRs().setHandler(handler); } nextEOLChar = true; break; } case StompDecoder.c: { if (isEscaping) { holder.append(StompDecoder.HEADER_SEPARATOR); isEscaping = false; } else { holder.append(b); } break; } case NEW_LINE: { nextEOLChar = false; if (whiteSpaceOnly) { // Headers are terminated by a blank line readingHeaders = false; break outer; } String headerValue = holder.getString(); holder.reset(); if (!headers.containsKey(headerName)) { headers.put(headerName, headerValue); } if (headerName.equals(Stomp.Headers.CONTENT_LENGTH)) { contentLength = Integer.parseInt(headerValue); } if (headerName.equals(Stomp.Headers.CONTENT_TYPE)) { contentType = headerValue; } whiteSpaceOnly = true; inHeaderName = true; headerValueWhitespace = false; break; } default: { whiteSpaceOnly = false; headerValueWhitespace = false; if (isEscaping) { throwUndefinedEscape(b); } holder.append(b); } } if (pos == data) { // Run out of data return false; } } return true; } @Override protected StompFrame parseBody() throws ActiveMQStompException { byte[] content = null; if (contentLength != -1) { if (pos + contentLength + 1 > data) { // Need more bytes } else { content = new byte[contentLength]; System.arraycopy(workingBuffer, pos, content, 0, contentLength); pos += contentLength; //drain all the rest if (bodyStart == -1) { bodyStart = pos; } while (pos < data) { if (workingBuffer[pos++] == 0) { break; } } } } else { // Need to scan for terminating NUL if (bodyStart == -1) { bodyStart = pos; } while (pos < data) { if (workingBuffer[pos++] == 0) { content = new byte[pos - bodyStart - 1]; System.arraycopy(workingBuffer, bodyStart, content, 0, content.length); break; } } } if (content != null) { if (data > pos) { if (workingBuffer[pos] == NEW_LINE) pos++; if (data > pos) // More data still in the buffer from the next packet System.arraycopy(workingBuffer, pos, workingBuffer, 0, data - pos); } data = data - pos; // reset StompFrame ret = new StompFrameV11(command, headers, content); init(); return ret; } else { return null; } } } }