001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.socket.sockjs.frame; 018 019import org.springframework.util.Assert; 020 021/** 022 * An base class for SockJS message codec that provides an implementation of 023 * {@link #encode(String[])}. 024 * 025 * @author Rossen Stoyanchev 026 * @since 4.0 027 */ 028public abstract class AbstractSockJsMessageCodec implements SockJsMessageCodec { 029 030 @Override 031 public String encode(String... messages) { 032 Assert.notNull(messages, "messages must not be null"); 033 StringBuilder sb = new StringBuilder(); 034 sb.append("a["); 035 for (int i = 0; i < messages.length; i++) { 036 sb.append('"'); 037 char[] quotedChars = applyJsonQuoting(messages[i]); 038 sb.append(escapeSockJsSpecialChars(quotedChars)); 039 sb.append('"'); 040 if (i < messages.length - 1) { 041 sb.append(','); 042 } 043 } 044 sb.append(']'); 045 return sb.toString(); 046 } 047 048 /** 049 * Apply standard JSON string quoting (see https://www.json.org/). 050 */ 051 protected abstract char[] applyJsonQuoting(String content); 052 053 /** 054 * See "JSON Unicode Encoding" section of SockJS protocol. 055 */ 056 private String escapeSockJsSpecialChars(char[] characters) { 057 StringBuilder result = new StringBuilder(); 058 for (char c : characters) { 059 if (isSockJsSpecialChar(c)) { 060 result.append('\\').append('u'); 061 String hex = Integer.toHexString(c).toLowerCase(); 062 for (int i = 0; i < (4 - hex.length()); i++) { 063 result.append('0'); 064 } 065 result.append(hex); 066 } 067 else { 068 result.append(c); 069 } 070 } 071 return result.toString(); 072 } 073 074 /** 075 * See `escapable_by_server` variable in the SockJS protocol test suite. 076 */ 077 private boolean isSockJsSpecialChar(char ch) { 078 return (ch <= '\u001F') || (ch >= '\u200C' && ch <= '\u200F') || 079 (ch >= '\u2028' && ch <= '\u202F') || (ch >= '\u2060' && ch <= '\u206F') || 080 (ch >= '\uFFF0') || (ch >= '\uD800' && ch <= '\uDFFF'); 081 } 082 083}