001package jmri.util; 002 003import java.io.BufferedReader; 004import java.io.File; 005import java.io.FileInputStream; 006import java.io.IOException; 007import java.io.InputStreamReader; 008import java.io.OutputStream; 009import java.io.OutputStreamWriter; 010import java.io.PrintWriter; 011import java.net.HttpURLConnection; 012import java.net.URI; 013import java.net.URISyntaxException; 014import java.net.URL; 015import java.net.URLConnection; 016import java.util.ArrayList; 017import java.util.List; 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020 021/** 022 * Sends multi-part HTTP POST requests to a web server 023 * <p> 024 * Based on 025 * http://www.codejava.net/java-se/networking/upload-files-by-sending-multipart-request-programmatically 026 * <hr> 027 * This file is part of JMRI. 028 * <p> 029 * JMRI is free software; you can redistribute it and/or modify it under the 030 * terms of version 2 of the GNU General Public License as published by the Free 031 * Software Foundation. See the "COPYING" file for a copy of this license. 032 * <p> 033 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 034 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 035 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 036 * 037 * @author Matthew Harris Copyright (C) 2014 038 */ 039public class MultipartMessage { 040 041 private final String boundary; 042 private static final String LINE_FEED = "\r\n"; 043 private final HttpURLConnection httpConn; 044 private final String charSet; 045 private final OutputStream outStream; 046 private final PrintWriter writer; 047 048 /** 049 * Constructor initialises a new HTTP POST request with content type set to 050 * 'multipart/form-data'. 051 * <p> 052 * This allows for additional binary data to be uploaded. 053 * 054 * @param requestURL URL to which this request should be sent 055 * @param charSet character set encoding of this message 056 * @throws IOException if {@link OutputStream} cannot be created 057 * @throws URISyntaxException if the requestURL has wrong syntax 058 */ 059 public MultipartMessage(String requestURL, String charSet) 060 throws IOException, URISyntaxException { 061 062 this.charSet = charSet; 063 064 // create unique multi-part message boundary 065 boundary = "===" + System.currentTimeMillis() + "==="; 066 URL url = new URI(requestURL).toURL(); 067 httpConn = (HttpURLConnection) url.openConnection(); 068 httpConn.setUseCaches(false); 069 httpConn.setDoOutput(true); 070 httpConn.setDoInput(true); 071 httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); 072 httpConn.setRequestProperty("User-Agent", "JMRI " + jmri.Version.getCanonicalVersion()); 073 outStream = httpConn.getOutputStream(); 074 writer = new PrintWriter(new OutputStreamWriter(outStream, this.charSet), true); 075 } 076 077 /** 078 * Adds form field data to the request 079 * 080 * @param name field name 081 * @param value field value 082 */ 083 public void addFormField(String name, String value) { 084 log.debug("add form field: {}; value: {}", name, value); 085 writer.append("--" + boundary).append(LINE_FEED); 086 writer.append( 087 "Content-Disposition: form-data; name=\"" + name 088 + "\"").append(LINE_FEED); 089 writer.append("Content-Type: text/plain; charset=" + charSet) 090 .append(LINE_FEED); 091 writer.append(LINE_FEED); 092 writer.append(value).append(LINE_FEED); 093 writer.flush(); 094 } 095 096 /** 097 * Adds an upload file section to the request. MIME type of the file is 098 * determined based on the file extension. 099 * 100 * @param fieldName name attribute in form <input name="{fieldName}" 101 * type="file" /> 102 * @param uploadFile file to be uploaded 103 * @throws IOException if problem adding file to request 104 */ 105 public void addFilePart(String fieldName, File uploadFile) throws IOException { 106 addFilePart(fieldName, uploadFile, URLConnection.guessContentTypeFromName(uploadFile.getName())); 107 } 108 109 /** 110 * Adds an upload file section to the request. MIME type of the file is 111 * explicitly set. 112 * 113 * @param fieldName name attribute in form <input name="{fieldName}" 114 * type="file" /> 115 * @param uploadFile file to be uploaded 116 * @param fileType MIME type of file 117 * @throws IOException if problem adding file to request 118 */ 119 public void addFilePart(String fieldName, File uploadFile, String fileType) throws IOException { 120 log.debug("add file field: {}; file: {}; type: {}", fieldName, uploadFile, fileType); 121 String fileName = uploadFile.getName(); 122 writer.append("--" + boundary).append(LINE_FEED); 123 writer.append( 124 "Content-Disposition: form-data; name=\"" + fieldName 125 + "\"; filename=\"" + fileName + "\"") 126 .append(LINE_FEED); 127 writer.append( 128 "Content-Type: " + fileType).append(LINE_FEED); 129 writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED); 130 writer.append(LINE_FEED); 131 writer.flush(); 132 133 try (FileInputStream inStream = new FileInputStream(uploadFile)) { 134 byte[] buffer = new byte[4096]; 135 int bytesRead; 136 while ((bytesRead = inStream.read(buffer)) != -1) { 137 outStream.write(buffer, 0, bytesRead); 138 } 139 outStream.flush(); 140 } 141 142 writer.append(LINE_FEED); 143 writer.flush(); 144 } 145 146 /** 147 * Adds a header field to the request 148 * 149 * @param name name of header field 150 * @param value value of header field 151 */ 152 public void addHeaderField(String name, String value) { 153 log.debug("add header field: {}; value: {}", name, value); 154 writer.append(name + ": " + value).append(LINE_FEED); 155 writer.flush(); 156 } 157 158 /** 159 * Finalise and send MultipartMessage to end-point. 160 * 161 * @return Responses from end-point as a List of Strings 162 * @throws IOException if problem sending MultipartMessage to end-point 163 */ 164 public List<String> finish() throws IOException { 165 List<String> response = new ArrayList<>(); 166 167 writer.append(LINE_FEED).flush(); 168 writer.append("--" + boundary + "--").append(LINE_FEED); 169 writer.close(); 170 171 // check server status code first 172 int status = httpConn.getResponseCode(); 173 if (status == HttpURLConnection.HTTP_OK) { 174 try (BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getInputStream()))) { 175 String line; 176 while ((line = reader.readLine()) != null) { 177 response.add(line); 178 } 179 } 180 httpConn.disconnect(); 181 } else { 182 throw new IOException("Server returned non-OK status: " + status); 183 } 184 185 return response; 186 } 187 188 private static final Logger log = LoggerFactory.getLogger(MultipartMessage.class); 189 190}