| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. 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. For additional information regarding |
| * copyright in this work, please see the NOTICE file in the top level |
| * directory of this distribution. |
| */ |
| package org.apache.abdera2.common.text; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.FilterInputStream; |
| import java.io.FilterOutputStream; |
| import java.io.FilterReader; |
| import java.io.FilterWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.Reader; |
| import java.io.UnsupportedEncodingException; |
| import java.io.Writer; |
| import java.nio.CharBuffer; |
| |
| import org.apache.abdera2.common.text.CharUtils.Profile; |
| |
| /** |
| * Performs URL Percent Encoding |
| */ |
| public final class UrlEncoding { |
| |
| private static final String DEFAULT_ENCODING = "UTF-8"; |
| public final static char[] HEX = |
| {'0', '1', '2', '3', '4', '5', '6', '7', |
| '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; |
| |
| private UrlEncoding() { |
| } |
| |
| private static void encode(Appendable sb, byte... bytes) { |
| encode(sb, 0, bytes.length, bytes); |
| } |
| |
| private static void encodeByte(Appendable sb, byte c) throws IOException { |
| sb.append(HEX[(c >> 4) & 0x0f]) |
| .append(HEX[(c >> 0) & 0x0f]); |
| } |
| |
| public static void hex(Appendable sb, int offset, int length, byte... bytes) { |
| try { |
| int lim = Math.min(bytes.length, offset+length); |
| while(lim-offset>0) |
| encodeByte(sb,bytes[offset++]); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static void encode(Appendable sb, int offset, int length, byte... bytes) { |
| try { |
| int lim = Math.min(bytes.length, offset+length); |
| while(lim-offset>0) { |
| sb.append("%"); |
| encodeByte(sb,bytes[offset++]); |
| } |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public static String encode(char... chars) { |
| return encode(chars, 0, chars.length, DEFAULT_ENCODING, new Profile[0]); |
| } |
| |
| public static String encode(char[] chars, Profile profile) { |
| return encode(chars, 0, chars.length, DEFAULT_ENCODING, new Profile[] {profile}); |
| } |
| |
| public static String encode(char[] chars, Profile... profiles) { |
| return encode(chars, 0, chars.length, DEFAULT_ENCODING, profiles); |
| } |
| |
| public static String encode(char[] chars, String enc) { |
| return encode(chars, 0, chars.length, enc, new Profile[0]); |
| } |
| |
| public static String encode(char[] chars, String enc, Profile profile) { |
| return encode(chars, 0, chars.length, enc, new Profile[] {profile}); |
| } |
| |
| public static String encode(char[] chars, String enc, Profile... profiles) { |
| return encode(chars, 0, chars.length, enc, profiles); |
| } |
| |
| public static String encode(char[] chars, int offset, int length) { |
| return encode(chars, offset, length, DEFAULT_ENCODING, new Profile[0]); |
| } |
| |
| public static String encode(char[] chars, int offset, int length, String enc) { |
| return encode(chars, offset, length, enc, new Profile[0]); |
| } |
| |
| public static String encode(char[] chars, int offset, int length, Profile profile) { |
| return encode(chars, offset, length, DEFAULT_ENCODING, new Profile[] {profile}); |
| } |
| |
| public static String encode(char[] chars, int offset, int length, Profile... profiles) { |
| return encode(chars, offset, length, DEFAULT_ENCODING, profiles); |
| } |
| |
| public static String encode(char[] chars, int offset, int length, String enc, Profile profile) { |
| return encode(chars, offset, length, enc, new Profile[] {profile}); |
| } |
| |
| public static String encode(char[] chars, int offset, int length, String enc, Profile... profiles) { |
| try { |
| return encode((CharSequence)CharBuffer.wrap(chars, offset, length), enc, profiles); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public static String encode(InputStream in) throws IOException { |
| StringBuilder buf = new StringBuilder(); |
| byte[] chunk = new byte[1024]; |
| int r = -1; |
| while ((r = in.read(chunk)) > -1) |
| encode(buf, 0, r, chunk); |
| return buf.toString(); |
| } |
| |
| public static String encode(InputStream in, String charset) throws IOException { |
| return encode(in, charset, DEFAULT_ENCODING, new Profile[0]); |
| } |
| |
| public static String encode(InputStream in, String charset, Profile profile) throws IOException { |
| return encode(in, charset, DEFAULT_ENCODING, new Profile[] {profile}); |
| } |
| |
| public static String encode(InputStream in, String charset, String enc) throws IOException { |
| return encode(in, charset, enc, new Profile[0]); |
| } |
| |
| public static String encode(InputStream in, String charset, String enc, Profile profile) throws IOException { |
| return encode(in, charset, enc, new Profile[] {profile}); |
| } |
| |
| public static String encode(InputStream in, String charset, String enc, Profile... profiles) throws IOException { |
| return encode(new InputStreamReader(in, charset), enc, profiles); |
| } |
| |
| public static String encode(InputStream in, String charset, Profile... profiles) throws IOException { |
| return encode(new InputStreamReader(in, charset), DEFAULT_ENCODING, profiles); |
| } |
| |
| public static String encode(Reader reader) throws IOException { |
| return encode(reader, DEFAULT_ENCODING, new Profile[0]); |
| } |
| |
| public static String encode(Readable readable) throws IOException { |
| return encode(readable, DEFAULT_ENCODING, new Profile[0]); |
| } |
| |
| public static String encode(Reader reader, String enc) throws IOException { |
| return encode(reader, enc, new Profile[0]); |
| } |
| |
| public static String encode(Readable readable, String enc) throws IOException { |
| return encode(readable, enc, new Profile[0]); |
| } |
| |
| public static String encode(Reader reader, String enc, Profile profile) throws IOException { |
| return encode(reader, enc, new Profile[] {profile}); |
| } |
| |
| public static String encode(Reader reader, Profile profile) throws IOException { |
| return encode(reader, DEFAULT_ENCODING, new Profile[] {profile}); |
| } |
| |
| public static String encode(Reader reader, Profile... profiles) throws IOException { |
| return encode(reader, DEFAULT_ENCODING, profiles); |
| } |
| |
| public static String encode(Readable readable, String enc, Profile profile) throws IOException { |
| return encode(readable, enc, new Profile[] {profile}); |
| } |
| |
| public static String encode(Readable readable, Profile profile) throws IOException { |
| return encode(readable, DEFAULT_ENCODING, new Profile[] {profile}); |
| } |
| |
| public static String encode(Readable readable, Profile... profiles) throws IOException { |
| return encode(readable, DEFAULT_ENCODING, profiles); |
| } |
| |
| private static void processChars(StringBuilder sb, CharBuffer chars, String enc, Profile... profiles) |
| throws IOException { |
| for (int n = 0; n < chars.length(); n++) { |
| char c = chars.charAt(n); |
| if (!Character.isHighSurrogate(c) && check(c, profiles)) { |
| encode(sb, String.valueOf(c).getBytes(enc)); |
| } else if (Character.isHighSurrogate(c)) { |
| if (check(c, profiles)) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append(c) |
| .append(chars.charAt(++n)); |
| byte[] b = buf.toString().getBytes(enc); |
| encode(sb, b); |
| } else { |
| sb.append(c) |
| .append(chars.charAt(++n)); |
| } |
| } else { |
| sb.append(c); |
| } |
| } |
| } |
| |
| public static String encode(Readable readable, String enc, Profile... profiles) throws IOException { |
| StringBuilder sb = new StringBuilder(); |
| CharBuffer chars = CharBuffer.allocate(1024); |
| while (readable.read(chars) > -1) { |
| chars.flip(); |
| processChars(sb, chars, enc, profiles); |
| } |
| return sb.toString(); |
| } |
| |
| public static String encode(Reader reader, String enc, Profile... profiles) throws IOException { |
| StringBuilder sb = new StringBuilder(); |
| char[] chunk = new char[1024]; |
| int r = -1; |
| while ((r = reader.read(chunk)) > -1) |
| processChars(sb, CharBuffer.wrap(chunk, 0, r), enc, profiles); |
| return sb.toString(); |
| } |
| |
| public static String encode(byte... bytes) { |
| StringBuilder buf = new StringBuilder(); |
| encode(buf, bytes); |
| return buf.toString(); |
| } |
| |
| public static String encode(byte[] bytes, int off, int len) { |
| StringBuilder buf = new StringBuilder(); |
| encode(buf, off, len, bytes); |
| return buf.toString(); |
| } |
| |
| public static String encode(CharSequence s) { |
| return encode(s, Profile.NONOP); |
| } |
| |
| public static String encode(CharSequence s, Profile profile) { |
| return encode(s, new Profile[] {profile}); |
| } |
| |
| public static String encode(CharSequence s, Profile... profiles) { |
| try { |
| if (s == null) |
| return null; |
| return encode(s, "utf-8", profiles); |
| } catch (UnsupportedEncodingException e) { |
| return null; // shouldn't happen |
| } |
| } |
| |
| public static String encode(CharSequence s, int offset, int length) { |
| return encode(s, offset, length, Profile.NONOP); |
| } |
| |
| public static String encode(CharSequence s, int offset, int length, Profile profile) { |
| return encode(s, offset, length, new Profile[] {profile}); |
| } |
| |
| public static String encode(CharSequence s, int offset, int length, Profile... profiles) { |
| try { |
| if (s == null) |
| return null; |
| return encode(s, offset, length, "utf-8", profiles); |
| } catch (UnsupportedEncodingException e) { |
| return null; // shouldn't happen |
| } |
| } |
| |
| private static boolean check(int codepoint, Profile... profiles) { |
| for (Profile profile : profiles) { |
| if (profile.filter(codepoint)) |
| return true; |
| } |
| return false; |
| } |
| |
| public static String encode(CharSequence s, int offset, int length, String enc, Profile... profiles) |
| throws UnsupportedEncodingException { |
| int end = Math.min(s.length(), offset + length); |
| CharSequence seq = s.subSequence(offset, end); |
| return encode(seq, enc, profiles); |
| } |
| |
| public static String encode(CharSequence s, String enc, Profile... profiles) throws UnsupportedEncodingException { |
| if (s == null) |
| return null; |
| StringBuilder sb = new StringBuilder(); |
| |
| for (int n = 0; n < s.length(); n++) { |
| char c = s.charAt(n); |
| if (!Character.isHighSurrogate(c) && check(c, profiles)) { |
| encode(sb, String.valueOf(c).getBytes(enc)); |
| } else if (Character.isHighSurrogate(c)) { |
| if (check(c, profiles)) { |
| StringBuilder buf = new StringBuilder(); |
| buf.append(c) |
| .append(s.charAt(++n)); |
| byte[] b = buf.toString().getBytes(enc); |
| encode(sb, b); |
| } else { |
| sb.append(c) |
| .append(s.charAt(++n)); |
| } |
| } else { |
| sb.append(c); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| public static String decode(String e, String enc) throws UnsupportedEncodingException { |
| try { |
| DecodingInputStream r = new DecodingInputStream(e.getBytes(enc)); |
| StringBuilder builder = new StringBuilder(); |
| byte[] buf = new byte[100]; |
| int i = -1; |
| while ((i = r.read(buf)) != -1) |
| builder.append(new String(buf,0,i,enc)); |
| return builder.toString(); |
| } catch (IOException i) { |
| throw new RuntimeException(i); |
| } |
| } |
| |
| public static String decode(String e) { |
| try { |
| return decode(e, "utf-8"); |
| } catch (Exception ex) { |
| return e; |
| } |
| } |
| |
| public static class EncodingOutputStream extends FilterOutputStream { |
| |
| public EncodingOutputStream(OutputStream out) { |
| super(out); |
| } |
| |
| @Override |
| public void write(byte[] b, int off, int len) throws IOException { |
| String enc = encode(b, off, len); |
| out.write(enc.getBytes(DEFAULT_ENCODING)); |
| } |
| |
| @Override |
| public void write(byte[] b) throws IOException { |
| String enc = encode(b); |
| out.write(enc.getBytes(DEFAULT_ENCODING)); |
| } |
| |
| @Override |
| public void write(int b) throws IOException { |
| String enc = encode((byte)b); |
| out.write(enc.getBytes(DEFAULT_ENCODING)); |
| } |
| } |
| |
| public static class EncodingWriter extends FilterWriter { |
| private final Profile[] profiles; |
| |
| public EncodingWriter(OutputStream out) { |
| this(new OutputStreamWriter(out)); |
| } |
| |
| public EncodingWriter(OutputStream out, Profile profile) { |
| this(new OutputStreamWriter(out), profile); |
| } |
| |
| public EncodingWriter(OutputStream out, Profile... profiles) { |
| this(new OutputStreamWriter(out), profiles); |
| } |
| |
| public EncodingWriter(Writer out) { |
| this(out, new Profile[0]); |
| } |
| |
| public EncodingWriter(Writer out, Profile profile) { |
| this(out, new Profile[] {profile}); |
| } |
| |
| public EncodingWriter(Writer out, Profile... profiles) { |
| super(out); |
| this.profiles = profiles; |
| } |
| |
| @Override |
| public void write(char[] b, int off, int len) throws IOException { |
| String enc = encode(b, off, len, profiles); |
| out.write(enc.toCharArray()); |
| } |
| |
| @Override |
| public void write(char[] b) throws IOException { |
| String enc = encode(b, profiles); |
| out.write(enc.toCharArray()); |
| } |
| |
| @Override |
| public void write(int b) throws IOException { |
| String enc = encode(new char[] {(char)b}, profiles); |
| out.write(enc.toCharArray()); |
| } |
| |
| @Override |
| public void write(String str, int off, int len) throws IOException { |
| String enc = encode(str, off, len, profiles); |
| out.write(enc.toCharArray()); |
| } |
| } |
| |
| public static class DecodingInputStream extends FilterInputStream { |
| public DecodingInputStream(InputStream in) { |
| super(in); |
| } |
| |
| public DecodingInputStream(byte[] in) { |
| super(new ByteArrayInputStream(in)); |
| } |
| |
| public int read() throws IOException { |
| int c = super.read(); |
| if (c == '%') { |
| return decode( |
| (char)super.read(), |
| (char)super.read()); |
| } else { |
| return c; |
| } |
| } |
| |
| @Override |
| public synchronized int read(byte[] b, int off, int len) throws IOException { |
| int n = off; |
| int i = -1; |
| while ((i = read()) != -1 && n < off + len) { |
| b[n++] = (byte)i; |
| } |
| int r = n - off; |
| return r==0?-1:n - off; |
| } |
| |
| @Override |
| public int read(byte[] b) throws IOException { |
| return read(b, 0, b.length); |
| } |
| |
| @Override |
| public long skip(long n) throws IOException { |
| long i = 0; |
| for (; i < n; i++) |
| read(); |
| return i; |
| } |
| |
| } |
| |
| public static class DecodingReader extends FilterReader { |
| public DecodingReader(byte[] buf) throws UnsupportedEncodingException { |
| this(new ByteArrayInputStream(buf)); |
| } |
| |
| public DecodingReader(byte[] buf, String enc) throws UnsupportedEncodingException { |
| this(new ByteArrayInputStream(buf), enc); |
| } |
| |
| public DecodingReader(InputStream in) throws UnsupportedEncodingException { |
| this(in, DEFAULT_ENCODING); |
| } |
| |
| public DecodingReader(InputStream in, String enc) throws UnsupportedEncodingException { |
| this(new InputStreamReader(in, enc)); |
| } |
| |
| public DecodingReader(Reader in) { |
| super(in); |
| } |
| |
| public int read() throws IOException { |
| int c = super.read(); |
| if (c == '%') { |
| int c1 = super.read(); |
| int c2 = super.read(); |
| return decode((char)c1, (char)c2); |
| } else { |
| return c; |
| } |
| } |
| |
| @Override |
| public synchronized int read(char[] b, int off, int len) throws IOException { |
| int n = off; |
| int i = -1; |
| while ((i = read()) != -1 && n < off + len) { |
| b[n++] = (char)i; |
| } |
| int r = n - off; |
| return r==0?-1:r; |
| } |
| |
| @Override |
| public int read(char[] b) throws IOException { |
| return read(b, 0, b.length); |
| } |
| |
| @Override |
| public long skip(long n) throws IOException { |
| long i = 0; |
| for (; i < n; i++) |
| read(); |
| return i; |
| } |
| } |
| |
| private static byte decode(char c, int shift) { |
| return (byte)((((c >= '0' && c <= '9') ? c - '0' : (c >= 'A' && c <= 'F') ? c - 'A' + 10 |
| : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1) & 0xf) << shift); |
| } |
| |
| private static byte decode(char c1, char c2) { |
| return (byte)(decode(c1, 4) | decode(c2, 0)); |
| } |
| |
| } |