Created
March 28, 2011 08:08
-
-
Save shin1ogawa/890144 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package appengine.util; | |
import java.io.UnsupportedEncodingException; | |
import java.net.HttpURLConnection; | |
import java.security.InvalidKeyException; | |
import java.security.NoSuchAlgorithmException; | |
/** | |
* @author shin1ogawa | |
*/ | |
public interface Auth { | |
/** | |
* @param connection | |
* @param body | |
* @throws Exception | |
*/ | |
void setup(HttpURLConnection connection, byte[] body) throws Exception; | |
/** | |
* Default implementation using ClientLogin. | |
* @author shin1ogawa | |
* @see <a href="http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html">ClientLogin for Installed Applications</a> | |
*/ | |
public static class ClientLogin implements Auth { | |
final String cookieName; | |
final String cookieValue; | |
/** | |
* the constructor. | |
* @param cookieValue | |
* @param useSsl | |
* @category constructor | |
*/ | |
public ClientLogin(String cookieValue, boolean useSsl) { | |
this.cookieValue = cookieValue; | |
this.cookieName = useSsl ? "SACSID" : "ACSID"; | |
} | |
@Override | |
public void setup(HttpURLConnection connection, byte[] body) { | |
} | |
} | |
/** | |
* Default implementaion usin OAuth. | |
* @author shin1ogawa | |
* @see <a href="http://code.google.com/apis/accounts/docs/OAuthForInstalledApps.html">OAuth for Installed Applications</a> | |
*/ | |
public static class OAuth implements Auth { | |
final String consumerKey; | |
final String consumerSecret; | |
final String accessToken; | |
final String tokenSecret; | |
/** | |
* the constructor. | |
* @param consumerKey | |
* @param consumerSecret | |
* @param accessToken | |
* @param tokenSecret | |
* @category constructor | |
*/ | |
public OAuth(String consumerKey, String consumerSecret, String accessToken, | |
String tokenSecret) { | |
super(); | |
this.consumerKey = consumerKey; | |
this.consumerSecret = consumerSecret; | |
this.accessToken = accessToken; | |
this.tokenSecret = tokenSecret; | |
} | |
@Override | |
public void setup(HttpURLConnection connection, byte[] body) throws InvalidKeyException, | |
UnsupportedEncodingException, NoSuchAlgorithmException { | |
String headerValue = | |
OAuthUtil.getAuthorizationHeaderValue(connection.getRequestMethod(), connection | |
.getURL().toString(), null, consumerKey, consumerSecret, accessToken, | |
tokenSecret); | |
connection.setRequestProperty("Authorization", headerValue); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package appengine.util; | |
import java.io.BufferedReader; | |
import java.io.IOException; | |
import java.io.InputStreamReader; | |
import java.io.OutputStreamWriter; | |
import java.io.PrintWriter; | |
import java.net.HttpURLConnection; | |
import java.net.URL; | |
import java.net.URLEncoder; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.logging.Level; | |
import java.util.logging.Logger; | |
/** | |
* Utilities for Google Client Login api. | |
* @author shin1ogawa | |
*/ | |
public class ClientLoginUtil { | |
static final Logger logger = Logger.getLogger(ClientLoginUtil.class.getName()); | |
static final String CLIENTLOGIN_URL = "https://www.google.com/accounts/ClientLogin"; | |
/** | |
* @param email | |
* @param password | |
* @param applicationName | |
* {yourname}-{applicationName}-{versionNumber} | |
* @param applicationUrl url of your application | |
* e.g. {@literal http://shin1ogawa.appspot.com/} | |
* @param continueUrl url of needs authenticate | |
* e.g. {@literal http://shin1ogawa.appspot.com/pathToNeedsAuthenticate} | |
* @return cookie value {@literal ACSID=....} or {@literal SACSID=....} | |
* @throws IOException | |
*/ | |
public static String getCookie(String email, String password, String applicationName, | |
String applicationUrl, String continueUrl) throws IOException { | |
String authToken = getAuthTokenUsingGoogleClientLogin(email, password, applicationName); | |
return getCookieUsingAppengineLogin(applicationUrl, continueUrl, authToken); | |
} | |
static String getAuthTokenUsingGoogleClientLogin(String email, String password, | |
String applicationName) throws IOException { | |
URL url = new URL(CLIENTLOGIN_URL); | |
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | |
connection.setRequestMethod("POST"); | |
connection.setDoOutput(true); | |
connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); | |
PrintWriter w = new PrintWriter(new OutputStreamWriter(connection.getOutputStream())); | |
w.print("Email=" + URLEncoder.encode(email, "utf-8")); | |
w.print("&Passwd=" + URLEncoder.encode(password, "utf-8")); | |
w.print("&service=" + URLEncoder.encode("ah", "utf-8")); | |
w.print("&source=" + URLEncoder.encode(applicationName, "utf-8")); | |
w.print("&accountType=" + URLEncoder.encode("HOSTED_OR_GOOGLE", "utf-8")); | |
w.flush(); | |
w.close(); | |
connection.connect(); | |
BufferedReader reader = | |
new BufferedReader(new InputStreamReader(connection.getInputStream())); | |
String line = null; | |
while ((line = reader.readLine()) != null) { | |
if (line.startsWith("Auth") == false) { | |
continue; | |
} | |
return line.substring(line.indexOf('=') + 1); | |
} | |
throw new AssertionError(); | |
} | |
static String getCookieUsingAppengineLogin(String applicationUrl, String continueUrl, | |
String authToken) throws IOException { | |
StringBuilder _url = | |
new StringBuilder(applicationUrl).append(applicationUrl.endsWith("/") ? "" : "/") | |
.append("_ah/login?").append("continue=") | |
.append(URLEncoder.encode(continueUrl, "utf-8")).append("&").append("auth=") | |
.append(URLEncoder.encode(authToken, "utf-8")); | |
if (logger.isLoggable(Level.FINE)) { | |
logger.log(Level.FINE, "GET " + _url); | |
} | |
URL url = new URL(_url.toString()); | |
boolean followRedirects = HttpURLConnection.getFollowRedirects(); | |
HttpURLConnection.setFollowRedirects(false); | |
try { | |
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | |
connection.setRequestProperty("X-appcfg-api-version", "dummy"); | |
connection.connect(); | |
if (logger.isLoggable(Level.FINE)) { | |
logger.log(Level.FINE, "responseCode=" + connection.getResponseCode()); | |
} | |
Map<String, List<String>> headers = connection.getHeaderFields(); | |
Iterator<Map.Entry<String, List<String>>> i = headers.entrySet().iterator(); | |
while (i.hasNext()) { | |
Map.Entry<String, List<String>> next = i.next(); | |
String key = next.getKey(); | |
if (key == null || key.equals("Set-Cookie") == false) { | |
continue; | |
} | |
List<String> values = next.getValue(); | |
if (values.isEmpty() == false) { | |
return values.get(0); | |
} else { | |
return null; | |
} | |
} | |
return null; | |
} finally { | |
HttpURLConnection.setFollowRedirects(followRedirects); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package appengine.servlet; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.net.HttpURLConnection; | |
import java.util.Date; | |
import java.util.logging.Level; | |
import java.util.logging.Logger; | |
import javax.servlet.http.HttpServlet; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.http.HttpServletResponse; | |
import com.google.apphosting.api.ApiProxy; | |
/** | |
* リクエストされたbyte配列を{@link ApiProxy#makeSyncCall(String, String, byte[])}へ引き渡し、 | |
* その結果をレスポンスするだけのServlet. | |
* <p> | |
* 特殊なサーブレットなので、web.xmlにてセキュリティを設定しておくのがおすすめ。 | |
* </p> | |
* <div> | |
* <ul> | |
* <li>HttpHeaderに以下を設定する。 | |
* <ul> | |
* <li>serviceName</li> | |
* <li>methodName</li> | |
* <li>applicationId</li> | |
* </ul> | |
* </li> | |
* <li>payloadにProtocolBufferで出力されたbyte配列を設定して{code POST}する。</li> | |
* <li>{@literal application/octet-stream}で | |
* {@link ApiProxy#makeSyncCall(String, String, byte[])}の結果を返すので、クライアント側でよしなに。</li> | |
* </ul> | |
* </div> | |
* | |
* @author shin1ogawa | |
*/ | |
public class MakeSyncCallServlet extends HttpServlet { | |
private static final long serialVersionUID = -7389134447989025199L; | |
private static final String APPLICATION_ID = "applicationId"; | |
private static final String METHOD_NAME = "methodName"; | |
private static final String SERVICE_NAME = "serviceName"; | |
private static Logger logger = Logger.getLogger(MakeSyncCallServlet.class.getName()); | |
@Override | |
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { | |
resp.setContentType("text/html"); | |
resp.setCharacterEncoding("utf-8"); | |
resp.getWriter().println("<html><head><title>makeSyncCallServlet</title></head><body>"); | |
resp.getWriter().println("<p>makeSyncCallServlet.</p>"); | |
resp.getWriter().println("<p>" + new Date(System.currentTimeMillis()) + "</p>"); | |
resp.getWriter().println("</body></html>"); | |
resp.getWriter().flush(); | |
} | |
@Override | |
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { | |
String serviceName = req.getHeader(SERVICE_NAME); | |
String methodName = req.getHeader(METHOD_NAME); | |
String applicationId = req.getHeader(APPLICATION_ID); | |
logger.fine(applicationId + ":" + serviceName + "#" + methodName); | |
if (validateParameters(resp, serviceName, methodName, applicationId) == false) { | |
return; | |
} | |
byte[] requestBytes = toByteArray(req.getInputStream()); | |
logger.fine(applicationId + ":" + serviceName + "#" + methodName + ": requestBytes.length=" | |
+ (requestBytes != null ? requestBytes.length : "null")); | |
byte[] responseBytes = null; | |
try { | |
@SuppressWarnings("unchecked") | |
byte[] bytes = | |
ApiProxy.getDelegate().makeSyncCall(ApiProxy.getCurrentEnvironment(), | |
serviceName, methodName, requestBytes); | |
responseBytes = bytes; | |
} catch (Throwable th) { | |
logger.log(Level.WARNING, applicationId + ":" + serviceName + "#" + methodName, th); | |
resp.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR); | |
resp.setContentType("text/plain"); | |
th.printStackTrace(resp.getWriter()); | |
resp.getWriter().flush(); | |
return; | |
} | |
if (responseBytes == null) { | |
logger.fine(serviceName + "#" + methodName + ": responseBytes == null."); | |
responseBytes = new byte[0]; | |
} else { | |
logger.fine(serviceName + "#" + methodName + ": responseBytes.length=" | |
+ responseBytes.length); | |
} | |
resp.setContentType("application/octet-stream"); | |
resp.getOutputStream().write(responseBytes); | |
resp.getOutputStream().flush(); | |
} | |
private boolean validateParameters(HttpServletResponse resp, String serviceName, | |
String methodName, String applicationId) throws IOException { | |
if (serviceName == null || serviceName.length() == 0) { | |
onErrorInParameters(resp, "serviceName was not specified."); | |
return false; | |
} | |
if (methodName == null || methodName.length() == 0) { | |
onErrorInParameters(resp, "methodName was not specified."); | |
return false; | |
} | |
if (applicationId == null || applicationId.length() == 0) { | |
onErrorInParameters(resp, "applicationId was not specified."); | |
return false; | |
} | |
// 念のためサーバ環境のApplicationIdと同じかどうか確認する。 | |
String serverApplicationId = ApiProxy.getCurrentEnvironment().getAppId(); | |
if (serverApplicationId.equals(applicationId) == false) { | |
onErrorInParameters(resp, "applicationId was not equals to " + serverApplicationId | |
+ "."); | |
return false; | |
} | |
return true; | |
} | |
private void onErrorInParameters(HttpServletResponse resp, String message) throws IOException { | |
logger.warning(message); | |
resp.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR); | |
resp.setContentType("text/plain"); | |
resp.getWriter().println(message); | |
resp.getWriter().flush(); | |
} | |
static byte[] toByteArray(InputStream is) throws IOException { | |
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
byte[] buffer = new byte[1024]; | |
long count = 0; | |
int n = 0; | |
while (-1 != (n = is.read(buffer))) { | |
bos.write(buffer, 0, n); | |
count += n; | |
} | |
return bos.toByteArray(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package appengine.util; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.net.HttpURLConnection; | |
import java.net.URL; | |
import java.util.concurrent.Future; | |
import java.util.logging.Level; | |
import java.util.logging.Logger; | |
import javax.servlet.http.HttpServletResponse; | |
import com.google.apphosting.api.ApiProxy; | |
import com.google.apphosting.api.ApiProxy.ApiConfig; | |
import com.google.apphosting.api.ApiProxy.ApiProxyException; | |
import com.google.apphosting.api.ApiProxy.Environment; | |
import com.google.apphosting.api.ApiProxy.LogRecord; | |
/** | |
* MakeSyncCallを行うサーブレットへ処理を委譲する{@link ApiProxy.Delegate}の実装。 | |
* @author shin1ogawa | |
*/ | |
public class MakeSyncCallServletDelegate implements ApiProxy.Delegate<Environment> { | |
static final Logger logger = Logger.getLogger(MakeSyncCallServletDelegate.class.getName()); | |
@SuppressWarnings("unchecked") | |
final ApiProxy.Delegate<Environment> parent = ApiProxy.getDelegate(); | |
String applicationId; | |
URL servletUrl; | |
Auth auth; | |
/** | |
* the constructor. | |
* @param applicationId your application id | |
* @param servletUrl url to makeSyncCallServlet | |
* @param auth {@link Auth}. it doesnt authenticate if {@code null}. | |
* @category constructor | |
*/ | |
public MakeSyncCallServletDelegate(String applicationId, URL servletUrl, Auth auth) { | |
this.auth = auth; | |
} | |
@Override | |
public void log(Environment environment, LogRecord logRecord) { | |
getOriginal().log(environment, logRecord); | |
} | |
@Override | |
public byte[] makeSyncCall(Environment environment, String serviceName, String methodName, | |
byte[] request) throws ApiProxyException { | |
try { | |
return protocolBufferOnHttp(environment, serviceName, methodName, request); | |
} catch (Exception e) { | |
throw new MakeSyncCallServletFailureException(e); | |
} | |
} | |
@Override | |
public Future<byte[]> makeAsyncCall(Environment arg0, String arg1, String arg2, byte[] arg3, | |
ApiConfig arg4) { | |
throw new UnsupportedOperationException(); | |
} | |
private byte[] protocolBufferOnHttp(Environment environment, String serviceName, | |
String methodName, final byte[] request) throws Exception { | |
HttpURLConnection connection = (HttpURLConnection) servletUrl.openConnection(); | |
connection.setRequestMethod("POST"); | |
connection.setDoOutput(true); | |
connection.setRequestProperty("Content-Type", "application/octet-stream"); | |
connection.setRequestProperty("Content-Length", String.valueOf(request.length)); | |
connection.setRequestProperty("serviceName", serviceName); | |
connection.setRequestProperty("methodName", methodName); | |
connection.setRequestProperty("applicationId", environment.getAppId()); | |
if (auth != null) { | |
auth.setup(connection, request); | |
} | |
OutputStream os = connection.getOutputStream(); | |
os.write(request); | |
os.flush(); | |
try { | |
connection.connect(); | |
int statusCode = connection.getResponseCode(); | |
if (statusCode != HttpServletResponse.SC_OK) { | |
if (connection.getResponseMessage() != null) { | |
logger.log(Level.WARNING, statusCode + ": " + connection.getResponseMessage()); | |
throw new MakeSyncCallServletFailureException(statusCode + ": " | |
+ connection.getResponseMessage()); | |
} | |
} | |
InputStream is = connection.getInputStream(); | |
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |
byte[] buffer = new byte[1024]; | |
int n = 0; | |
while (-1 != (n = is.read(buffer))) { | |
bos.write(buffer, 0, n); | |
} | |
return bos.toByteArray(); | |
} catch (IOException e) { | |
throw new MakeSyncCallServletFailureException(e); | |
} | |
} | |
/** | |
* @return 元々ApiProxyに設定されていた{@link ApiProxy.Delegate}のインスタンス | |
*/ | |
public ApiProxy.Delegate<Environment> getOriginal() { | |
return parent; | |
} | |
/** | |
* {@link MakeSyncCallServletDelegate#makeSyncCall(Environment, String, String, byte[])} | |
* を実行中に{@link Exception}が発生した場合にそれをラップして投げ直す{@link RuntimeException}. | |
* @author shin1ogawa | |
*/ | |
public static class MakeSyncCallServletFailureException extends RuntimeException { | |
private static final long serialVersionUID = -6963701246227196182L; | |
/** | |
* the constructor. | |
* @param message | |
* @category constructor | |
*/ | |
public MakeSyncCallServletFailureException(String message) { | |
super(message); | |
} | |
/** | |
* the constructor. | |
* @param cause | |
* @category constructor | |
*/ | |
public MakeSyncCallServletFailureException(Throwable cause) { | |
super(cause); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package appengine.util; | |
import java.io.UnsupportedEncodingException; | |
import java.net.URLEncoder; | |
import java.security.InvalidKeyException; | |
import java.security.NoSuchAlgorithmException; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import java.util.LinkedHashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Random; | |
import javax.crypto.Mac; | |
import javax.crypto.spec.SecretKeySpec; | |
/** | |
* utilities for oauth of google app engine. | |
* @author shin1ogawa | |
*/ | |
public class OAuthUtil { | |
static final String ENC = "utf-8"; | |
static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; | |
/** | |
* @param method | |
* @param requestUrlBase | |
* @param parameters | |
* @param consumerKey | |
* @param consumerSecret | |
* @param accessToken | |
* @param accessTokenSecret | |
* @return value for Authorization Header | |
* @throws UnsupportedEncodingException | |
* @throws InvalidKeyException | |
* @throws NoSuchAlgorithmException | |
* @author shin1ogawa | |
*/ | |
public static String getAuthorizationHeaderValue(String method, String requestUrlBase, | |
Map<String, String> parameters, String consumerKey, String consumerSecret, | |
String accessToken, String accessTokenSecret) throws UnsupportedEncodingException, | |
InvalidKeyException, NoSuchAlgorithmException { | |
Map<String, String> _params = new LinkedHashMap<String, String>(); | |
if (parameters != null && parameters.isEmpty() == false) { | |
_params.putAll(parameters); | |
} | |
_params.put("oauth_consumer_key", consumerKey); | |
_params.put("oauth_nonce", generateNonce()); | |
_params.put("oauth_signature_method", "HMAC-SHA1"); | |
_params.put("oauth_timestamp", generateTimestamp()); | |
_params.put("oauth_token", accessToken); | |
_params.put("oauth_version", "1.0"); | |
String baseString = generateSignatureBaseString(method, requestUrlBase, _params); | |
String key = | |
URLEncoder.encode(consumerKey, "utf-8") + "&" | |
+ URLEncoder.encode(accessTokenSecret, "utf-8"); | |
String signature = URLEncoder.encode(generateHmacSha1Signature(key, baseString), ENC); | |
StringBuilder b = new StringBuilder("OAuth "); | |
if (_params.containsKey("oauth_body_hash")) { | |
b.append("oauth_body_hash").append("=\"").append(_params.get("oauth_body_hash")) | |
.append("\","); | |
} | |
b.append("oauth_consumer_key").append("=\"") | |
.append(URLEncoder.encode(consumerKey, "utf-8")).append("\""); | |
b.append(",oauth_token").append("=\"").append(URLEncoder.encode(accessToken, "utf-8")) | |
.append("\""); | |
b.append(",oauth_version").append("=\"").append("1.0").append("\""); | |
b.append(",oauth_timestamp").append("=\"").append(_params.get("oauth_timestamp")) | |
.append("\""); | |
b.append(",oauth_nonce").append("=\"").append(_params.get("oauth_nonce")).append("\""); | |
b.append(",oauth_signature_method").append("=\"").append("HMAC-SHA1").append("\""); | |
b.append(",oauth_signature").append("=\"").append(signature).append("\""); | |
return b.toString(); | |
} | |
static String generateSignatureBaseString(String method, String requestUrlBase, | |
Map<String, String> params) throws UnsupportedEncodingException { | |
return escapeAndJoinStrings(Arrays.asList(method, requestUrlBase, | |
escapeAndJoinParameters(params))); | |
} | |
static String escapeAndJoinParameters(Map<String, String> params) | |
throws UnsupportedEncodingException { | |
StringBuilder b = new StringBuilder(); | |
boolean first = true; | |
for (String key : sortedKeyList(params)) { | |
if (first == false) { | |
b.append('&'); | |
} else { | |
first = false; | |
} | |
b.append(key).append('=') | |
.append(URLEncoder.encode(params.get(key), ENC).replace("+", "%20")); | |
} | |
return b.toString(); | |
} | |
static List<String> sortedKeyList(Map<String, String> map) { | |
List<String> list = new ArrayList<String>(map.keySet()); | |
Collections.sort(list); | |
return list; | |
} | |
static String escapeAndJoinStrings(Iterable<String> params) throws UnsupportedEncodingException { | |
StringBuilder b = new StringBuilder(); | |
boolean first = true; | |
for (String s : params) { | |
if (first == false) { | |
b.append('&'); | |
} else { | |
first = false; | |
} | |
b.append(URLEncoder.encode(s, ENC)); | |
} | |
return b.toString(); | |
} | |
static String generateHmacSha1Signature(String key, String data) | |
throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, | |
UnsupportedEncodingException { | |
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(ENC), HMAC_SHA1_ALGORITHM); | |
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); | |
mac.init(signingKey); | |
return byteArrayToBase64String(mac.doFinal(data.getBytes(ENC))); | |
} | |
static String generateTimestamp() { | |
return Long.toString(System.currentTimeMillis() / 1000L); | |
} | |
static String generateNonce() { | |
return Long.toString(new Random().nextLong()); | |
} | |
static String byteArrayToBase64String(byte[] bytes) { | |
int bytesLength = bytes.length; | |
int fullGroups = bytesLength / 3; | |
int partialGroup = bytesLength - 3 * fullGroups; | |
int resultLength = 4 * ((bytesLength + 2) / 3); | |
StringBuilder b = new StringBuilder(resultLength); | |
int cursor = 0; | |
for (int i = 0; i < fullGroups; i++) { | |
int byte0 = bytes[cursor++] & 0xff; | |
int byte1 = bytes[cursor++] & 0xff; | |
int byte2 = bytes[cursor++] & 0xff; | |
b.append(intToBase64[byte0 >> 2]); | |
b.append(intToBase64[(byte0 << 4) & 0x3f | (byte1 >> 4)]); | |
b.append(intToBase64[(byte1 << 2) & 0x3f | (byte2 >> 6)]); | |
b.append(intToBase64[byte2 & 0x3f]); | |
} | |
if (partialGroup != 0) { | |
int byte0 = bytes[cursor++] & 0xff; | |
b.append(intToBase64[byte0 >> 2]); | |
if (partialGroup == 1) { | |
b.append(intToBase64[(byte0 << 4) & 0x3f]); | |
b.append("=="); | |
} else { | |
int byte1 = bytes[cursor++] & 0xff; | |
b.append(intToBase64[(byte0 << 4) & 0x3f | (byte1 >> 4)]); | |
b.append(intToBase64[(byte1 << 2) & 0x3f]); | |
b.append('='); | |
} | |
} | |
return b.toString(); | |
} | |
static final char intToBase64[] = { | |
'A', | |
'B', | |
'C', | |
'D', | |
'E', | |
'F', | |
'G', | |
'H', | |
'I', | |
'J', | |
'K', | |
'L', | |
'M', | |
'N', | |
'O', | |
'P', | |
'Q', | |
'R', | |
'S', | |
'T', | |
'U', | |
'V', | |
'W', | |
'X', | |
'Y', | |
'Z', | |
'a', | |
'b', | |
'c', | |
'd', | |
'e', | |
'f', | |
'g', | |
'h', | |
'i', | |
'j', | |
'k', | |
'l', | |
'm', | |
'n', | |
'o', | |
'p', | |
'q', | |
'r', | |
's', | |
't', | |
'u', | |
'v', | |
'w', | |
'x', | |
'y', | |
'z', | |
'0', | |
'1', | |
'2', | |
'3', | |
'4', | |
'5', | |
'6', | |
'7', | |
'8', | |
'9', | |
'+', | |
'/' | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment