blob: 652649cacb03c22f5667b68b7e2f820af9583b21 [file] [log] [blame]
/*
* 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));
}
}