mirror of
https://github.com/google/nomulus.git
synced 2025-05-02 21:17:50 +02:00
According to RFC 2046, the body of the multipart contains: multipart-body := [preamble CRLF] dash-boundary transport-padding CRLF body-part *encapsulation close-delimiter transport-padding [CRLF epilogue] The preemble and epilogue are optional, and ignored. However, it's not 100% explicit whether the CRLFs after the preamble and before the epilogue are required. The one after the preemble is often not given if there's no preemble, so it's conceivable that you don't *have* to give the CRLF before the epilogue if there's no epilogue (it's also enclosed in the [], making it part of the "optional") However, it seems that when the TMDB "migrated to the cloud" (as they describe it) on Aug. 13 2018, they started requiring that CRLF. TESTED=connected to a TMDB-whitelisted server, used CURL to manually create the message as we currently send it (without the final CRLF) with junk data and got the error from the bug. Then sent the exact same message with the additional CRLF, and got a different error that directly relates to the content of the junk data. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=212637246
106 lines
4.4 KiB
Java
106 lines
4.4 KiB
Java
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package google.registry.util;
|
|
|
|
import static com.google.common.base.Preconditions.checkState;
|
|
import static com.google.common.io.BaseEncoding.base64;
|
|
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
|
import static com.google.common.net.HttpHeaders.CONTENT_DISPOSITION;
|
|
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
|
|
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
|
import static java.lang.String.format;
|
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
|
|
import com.google.appengine.api.urlfetch.HTTPHeader;
|
|
import com.google.appengine.api.urlfetch.HTTPRequest;
|
|
import com.google.appengine.api.urlfetch.HTTPResponse;
|
|
import com.google.common.base.Ascii;
|
|
import com.google.common.base.Strings;
|
|
import com.google.common.net.MediaType;
|
|
import java.util.Optional;
|
|
import java.util.Random;
|
|
|
|
/** Helper methods for the App Engine URL fetch service. */
|
|
public final class UrlFetchUtils {
|
|
|
|
@NonFinalForTesting
|
|
private static Random random = new Random();
|
|
|
|
/** Returns value of first header matching {@code name}. */
|
|
public static Optional<String> getHeaderFirst(HTTPResponse rsp, String name) {
|
|
return getHeaderFirstInternal(rsp.getHeadersUncombined(), name);
|
|
}
|
|
|
|
/** Returns value of first header matching {@code name}. */
|
|
public static Optional<String> getHeaderFirst(HTTPRequest req, String name) {
|
|
return getHeaderFirstInternal(req.getHeaders(), name);
|
|
}
|
|
|
|
private static Optional<String> getHeaderFirstInternal(Iterable<HTTPHeader> hdrs, String name) {
|
|
name = Ascii.toLowerCase(name);
|
|
for (HTTPHeader header : hdrs) {
|
|
if (Ascii.toLowerCase(header.getName()).equals(name)) {
|
|
return Optional.of(header.getValue());
|
|
}
|
|
}
|
|
return Optional.empty();
|
|
}
|
|
|
|
/**
|
|
* Sets payload on request as a {@code multipart/form-data} request.
|
|
*
|
|
* <p>This is equivalent to running the command: {@code curl -F fieldName=@payload.txt URL}
|
|
*
|
|
* @see <a href="http://www.ietf.org/rfc/rfc2388.txt"> RFC2388 - Returning Values from Forms</a>
|
|
*/
|
|
public static void setPayloadMultipart(
|
|
HTTPRequest request, String name, String filename, MediaType contentType, String data) {
|
|
String boundary = createMultipartBoundary();
|
|
checkState(
|
|
!data.contains(boundary),
|
|
"Multipart data contains autogenerated boundary: %s", boundary);
|
|
StringBuilder multipart = new StringBuilder();
|
|
multipart.append(format("--%s\r\n", boundary));
|
|
multipart.append(format("%s: form-data; name=\"%s\"; filename=\"%s\"\r\n",
|
|
CONTENT_DISPOSITION, name, filename));
|
|
multipart.append(format("%s: %s\r\n", CONTENT_TYPE, contentType.toString()));
|
|
multipart.append("\r\n");
|
|
multipart.append(data);
|
|
multipart.append("\r\n");
|
|
multipart.append(format("--%s--\r\n", boundary));
|
|
byte[] payload = multipart.toString().getBytes(UTF_8);
|
|
request.addHeader(
|
|
new HTTPHeader(CONTENT_TYPE, format("multipart/form-data; boundary=\"%s\"", boundary)));
|
|
request.addHeader(new HTTPHeader(CONTENT_LENGTH, Integer.toString(payload.length)));
|
|
request.setPayload(payload);
|
|
}
|
|
|
|
private static String createMultipartBoundary() {
|
|
// Generate 192 random bits (24 bytes) to produce 192/log_2(64) = 192/6 = 32 base64 digits.
|
|
byte[] rand = new byte[24];
|
|
random.nextBytes(rand);
|
|
// Boundary strings can be up to 70 characters long, so use 30 hyphens plus 32 random digits.
|
|
// See https://tools.ietf.org/html/rfc2046#section-5.1.1
|
|
return Strings.repeat("-", 30) + base64().encode(rand);
|
|
}
|
|
|
|
/** Sets the HTTP Basic Authentication header on an {@link HTTPRequest}. */
|
|
public static void setAuthorizationHeader(HTTPRequest req, Optional<String> login) {
|
|
if (login.isPresent()) {
|
|
String token = base64().encode(login.get().getBytes(UTF_8));
|
|
req.addHeader(new HTTPHeader(AUTHORIZATION, "Basic " + token));
|
|
}
|
|
}
|
|
}
|