1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.compatibility.common.util; 18 19 import com.google.errorprone.annotations.CanIgnoreReturnValue; 20 21 import java.io.ByteArrayOutputStream; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.OutputStream; 25 import java.io.OutputStreamWriter; 26 import java.io.PrintWriter; 27 import java.net.HttpURLConnection; 28 import java.net.URL; 29 import java.util.HashMap; 30 import java.util.Map; 31 32 /** Builds a multipart form and submits it. */ 33 class MultipartForm { 34 35 private static final String FORM_DATA_BOUNDARY = "C75I55u3R3p0r73r"; 36 37 /* package */ final String mServerUrl; 38 /* package */ final Map<String, String> mFormValues = new HashMap<String, String>(); 39 /* package */ String mName; 40 /* package */ String mFileName; 41 /* package */ byte[] mData; 42 43 /** 44 * Creates a new multi-part form with the given serverUrl. 45 */ MultipartForm(String serverUrl)46 public MultipartForm(String serverUrl) { 47 mServerUrl = serverUrl; 48 } 49 50 /** 51 * Adds a key value attribute to the form. 52 * 53 * @param name the name of the attribute. 54 * @param value the attribute's value. 55 * @return the {@link MultipartForm} for easy chaining. 56 */ 57 @CanIgnoreReturnValue addFormValue(String name, String value)58 public MultipartForm addFormValue(String name, String value) { 59 mFormValues.put(name, value); 60 return this; 61 } 62 63 /** 64 * Adds the file as the payload of the form. 65 * 66 * @param name The name of attribute 67 * @param fileName The file's name 68 * @param data The file's data 69 * @return the {@link MultipartForm} for easy chaining. 70 */ 71 @CanIgnoreReturnValue addFormFile(String name, String fileName, byte[] data)72 public MultipartForm addFormFile(String name, String fileName, byte[] data) { 73 mName = name; 74 mFileName = fileName; 75 mData = data; 76 return this; 77 } 78 79 /** 80 * Submits the form to the server url. 81 * 82 * This will handle a redirection from the server. 83 * 84 * @return response code 85 * @throws IOException 86 */ submit()87 public int submit() throws IOException { 88 return submitForm(mServerUrl); 89 } 90 91 /** 92 * @param serverUrl to post the data to 93 * @return response code 94 * @throws IOException 95 */ submitForm(String serverUrl)96 private int submitForm(String serverUrl) throws IOException { 97 HttpURLConnection connection = null; 98 try { 99 URL url = new URL(serverUrl); 100 connection = (HttpURLConnection) url.openConnection(); 101 connection.setInstanceFollowRedirects(false); 102 connection.setRequestMethod("POST"); 103 connection.setDoOutput(true); 104 connection.setRequestProperty("Content-Type", 105 "multipart/form-data; boundary=" + FORM_DATA_BOUNDARY); 106 107 byte[] body = getContentBody(); 108 connection.setRequestProperty("Content-Length", Integer.toString(body.length)); 109 110 OutputStream output = connection.getOutputStream(); 111 try { 112 output.write(body); 113 } finally { 114 output.close(); 115 } 116 117 // Open the stream to get a response. Otherwise request will be cancelled. 118 InputStream input = connection.getInputStream(); 119 input.close(); 120 121 int response = connection.getResponseCode(); 122 if (response == 302) { 123 return submitForm(connection.getHeaderField("Location")); 124 } 125 return response; 126 } finally { 127 if (connection != null) { 128 connection.disconnect(); 129 } 130 } 131 } 132 getContentBody()133 /* package */ byte[] getContentBody() throws IOException { 134 ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); 135 PrintWriter writer = new PrintWriter(new OutputStreamWriter(byteOutput)); 136 writer.println(); 137 138 for (Map.Entry<String, String> formValue : mFormValues.entrySet()) { 139 writeFormField(writer, formValue.getKey(), formValue.getValue()); 140 } 141 142 if (mData != null) { 143 writeFormFileHeader(writer, mName, mFileName); 144 writer.flush(); // Must flush here before writing to the byte stream! 145 byteOutput.write(mData); 146 writer.println(); 147 } 148 writer.append("--").append(FORM_DATA_BOUNDARY).println("--"); 149 writer.flush(); 150 writer.close(); 151 return byteOutput.toByteArray(); 152 } 153 writeFormField(PrintWriter writer, String name, String value)154 private void writeFormField(PrintWriter writer, String name, String value) { 155 writer.append("--").println(FORM_DATA_BOUNDARY); 156 writer.append("Content-Disposition: form-data; name=\"").append(name).println("\""); 157 writer.println(); 158 writer.println(value); 159 } 160 writeFormFileHeader(PrintWriter writer, String name, String fileName)161 private void writeFormFileHeader(PrintWriter writer, String name, String fileName) { 162 writer.append("--").println(FORM_DATA_BOUNDARY); 163 writer.append("Content-Disposition: form-data; name=\"").append(name); 164 writer.append("\"; filename=\"").append(fileName).println("\""); 165 writer.println("Content-Type: application/x-gzip"); 166 writer.println("Content-Transfer-Encoding: binary"); 167 writer.println(); 168 } 169 } 170