Skip to content

Instantly share code, notes, and snippets.

@mariotaku
Created April 19, 2017 19:27
Show Gist options
  • Save mariotaku/77b81a781bd08988a8cb025885345c1b to your computer and use it in GitHub Desktop.
Save mariotaku/77b81a781bd08988a8cb025885345c1b to your computer and use it in GitHub Desktop.
/*
* Twidere - Twitter client for Android
*
* 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.mariotaku.twidere.util.net;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Base64;
import android.util.Base64OutputStream;
import org.mariotaku.restfu.Pair;
import org.mariotaku.restfu.http.ContentType;
import org.mariotaku.restfu.http.MultiValueMap;
import org.mariotaku.restfu.http.mime.Body;
import org.mariotaku.restfu.http.mime.BodyConverter;
import org.mariotaku.restfu.http.mime.StringBody;
import org.mariotaku.restfu.http.mime.UrlSerialization;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;
/**
* Created by mariotaku on 2017/4/19.
*/
public class Base64BinaryFormBodyConverter implements BodyConverter {
@NonNull
@Override
public Body convert(@NonNull MultiValueMap<Body> params, @NonNull String[] args) {
boolean proximateSize = false, dataUri = false;
for (String arg : args) {
switch (arg) {
case "proximate_size": {
proximateSize = true;
break;
}
case "data_uri": {
dataUri = true;
break;
}
}
}
return new Base64BinaryFormBody(params.toList(), proximateSize, dataUri);
}
public static class Base64BinaryFormBody implements Body {
private final List<Pair<String, Body>> params;
private final boolean proximateSize;
private final boolean dataUri;
private final Charset charset;
public Base64BinaryFormBody(List<Pair<String, Body>> params, boolean proximateSize,
boolean dataUri) {
this.params = params;
this.proximateSize = proximateSize;
this.dataUri = dataUri;
this.charset = Charset.forName("UTF-8");
}
@Override
public ContentType contentType() {
return new ContentType("application/x-www-form-urlencoded", charset);
}
@Override
public String contentEncoding() {
return null;
}
@Override
public long length() throws IOException {
final LengthCountOutputStream stream = new LengthCountOutputStream();
writeTo(stream);
return stream.length();
}
@Override
public long writeTo(OutputStream os) throws IOException {
long write = 0;
for (Pair<String, Body> param : params) {
final byte[] nameBytes = encode(param.first);
write += write(os, nameBytes);
final Body value = param.second;
if (value == null) {
continue;
}
write += write(os, "=".getBytes(charset));
if (value instanceof StringBody) {
final byte[] valueBytes = encode(((StringBody) value).value());
write += write(os, valueBytes);
} else {
if (dataUri) {
write += write(os, encode("data:"));
String type = getContentType(value);
write += write(os, encode(type != null ? type : "application/octet-stream"));
write += write(os, encode(";base64,"));
}
long encodedLength = predictBase64Size(value.length(), true, false);
if (os instanceof LengthCountOutputStream) {
((LengthCountOutputStream) os).add(encodedLength);
} else {
value.writeTo(new Base64OutputStream(os, Base64.NO_WRAP | Base64.URL_SAFE));
}
write += encodedLength;
}
}
return write;
}
@Override
public InputStream stream() throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
writeTo(os);
return new ByteArrayInputStream(os.toByteArray());
}
@Override
public void close() throws IOException {
for (Pair<String, Body> param : params) {
param.second.close();
}
}
private byte[] encode(String value) {
return UrlSerialization.QUERY.serialize(value, charset).getBytes(charset);
}
private static long predictBase64Size(long inputSize, boolean padded, boolean crlf) {
long encodedSize = inputSize / 3 * 4;
if (padded && inputSize % 3 != 0) {
encodedSize += 4;
}
if (crlf) {
encodedSize += 2 + (2 * encodedSize / 72);
}
return encodedSize;
}
private static long write(OutputStream os, byte[] bytes) throws IOException {
os.write(bytes);
return bytes.length;
}
@Nullable
private static String getContentType(Body body) {
ContentType type = body.contentType();
if (type == null) return null;
return type.getContentType();
}
}
private static class LengthCountOutputStream extends OutputStream {
private boolean noLength;
private long length;
LengthCountOutputStream() {
reset();
}
@Override
public void write(@NonNull final byte[] buffer, final int offset, final int count) throws IOException {
add(count);
}
@Override
public void write(final int oneByte) throws IOException {
add(1);
}
public void add(long added) {
length += added;
}
public void markNoLength() {
noLength = true;
}
public void reset() {
length = 0;
noLength = false;
}
public long length() {
if (noLength) return -1;
return length;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment