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 java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.io.OutputStreamWriter;
24 import java.io.PrintWriter;
25 import java.net.HttpURLConnection;
26 import java.net.URL;
27 import java.util.HashMap;
28 import java.util.Map;
29 
30 /** Builds a multipart form and submits it. */
31 class MultipartForm {
32 
33     private static final String FORM_DATA_BOUNDARY = "C75I55u3R3p0r73r";
34 
35     /* package */ final String mServerUrl;
36     /* package */ final Map<String, String> mFormValues = new HashMap<String, String>();
37     /* package */ String mName;
38     /* package */ String mFileName;
39     /* package */ byte[] mData;
40 
41     /**
42      * Creates a new multi-part form with the given serverUrl.
43      */
MultipartForm(String serverUrl)44     public MultipartForm(String serverUrl) {
45         mServerUrl = serverUrl;
46     }
47 
48     /**
49      * Adds a key value attribute to the form.
50      *
51      * @param name the name of the attribute.
52      * @param value the attribute's value.
53      * @return the {@link MultipartForm} for easy chaining.
54      */
addFormValue(String name, String value)55     public MultipartForm addFormValue(String name, String value) {
56         mFormValues.put(name, value);
57         return this;
58     }
59 
60     /**
61      * Adds the file as the payload of the form.
62      *
63      * @param name The name of attribute
64      * @param fileName The file's name
65      * @param data The file's data
66      * @return the {@link MultipartForm} for easy chaining.
67      */
addFormFile(String name, String fileName, byte[] data)68     public MultipartForm addFormFile(String name, String fileName, byte[] data) {
69         mName = name;
70         mFileName = fileName;
71         mData = data;
72         return this;
73     }
74 
75     /**
76      * Submits the form to the server url.
77      *
78      * This will handle a redirection from the server.
79      *
80      * @return response code
81      * @throws IOException
82      */
submit()83     public int submit() throws IOException {
84         return submitForm(mServerUrl);
85     }
86 
87     /**
88      * @param serverUrl to post the data to
89      * @return response code
90      * @throws IOException
91      */
submitForm(String serverUrl)92     private int submitForm(String serverUrl) throws IOException {
93         HttpURLConnection connection = null;
94         try {
95             URL url = new URL(serverUrl);
96             connection = (HttpURLConnection) url.openConnection();
97             connection.setInstanceFollowRedirects(false);
98             connection.setRequestMethod("POST");
99             connection.setDoOutput(true);
100             connection.setRequestProperty("Content-Type",
101                     "multipart/form-data; boundary=" + FORM_DATA_BOUNDARY);
102 
103             byte[] body = getContentBody();
104             connection.setRequestProperty("Content-Length", Integer.toString(body.length));
105 
106             OutputStream output = connection.getOutputStream();
107             try {
108                 output.write(body);
109             } finally {
110                 output.close();
111             }
112 
113             // Open the stream to get a response. Otherwise request will be cancelled.
114             InputStream input = connection.getInputStream();
115             input.close();
116 
117             int response = connection.getResponseCode();
118             if (response == 302) {
119                 return submitForm(connection.getHeaderField("Location"));
120             }
121             return response;
122         } finally {
123             if (connection != null) {
124                 connection.disconnect();
125             }
126         }
127     }
128 
getContentBody()129     /* package */ byte[] getContentBody() throws IOException {
130         ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
131         PrintWriter writer = new PrintWriter(new OutputStreamWriter(byteOutput));
132         writer.println();
133 
134         for (Map.Entry<String, String> formValue : mFormValues.entrySet()) {
135             writeFormField(writer, formValue.getKey(), formValue.getValue());
136         }
137 
138         if (mData != null) {
139             writeFormFileHeader(writer, mName, mFileName);
140             writer.flush(); // Must flush here before writing to the byte stream!
141             byteOutput.write(mData);
142             writer.println();
143         }
144         writer.append("--").append(FORM_DATA_BOUNDARY).println("--");
145         writer.flush();
146         writer.close();
147         return byteOutput.toByteArray();
148     }
149 
writeFormField(PrintWriter writer, String name, String value)150     private void writeFormField(PrintWriter writer, String name, String value) {
151         writer.append("--").println(FORM_DATA_BOUNDARY);
152         writer.append("Content-Disposition: form-data; name=\"").append(name).println("\"");
153         writer.println();
154         writer.println(value);
155     }
156 
writeFormFileHeader(PrintWriter writer, String name, String fileName)157     private void writeFormFileHeader(PrintWriter writer, String name, String fileName) {
158         writer.append("--").println(FORM_DATA_BOUNDARY);
159         writer.append("Content-Disposition: form-data; name=\"").append(name);
160         writer.append("\"; filename=\"").append(fileName).println("\"");
161         writer.println("Content-Type: application/x-gzip");
162         writer.println("Content-Transfer-Encoding: binary");
163         writer.println();
164     }
165 }
166