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