package org.jenkinsci.plugins.github.webhook;
import com.cloudbees.jenkins.GitHubWebHook;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.io.IOUtils;
import org.jenkinsci.plugins.github.util.misc.NullSafeFunction;
import org.kohsuke.stapler.AnnotationHandler;
import org.kohsuke.stapler.InjectedParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.slf4j.Logger;
import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.Map;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.apache.commons.lang3.Validate.notNull;
import static org.slf4j.LoggerFactory.getLogger;
/**
* InjectedParameter annotation to use on WebMethod parameters.
* Handles GitHub's payload of webhook
*
* @author lanwen (Merkushev Kirill)
* @see <a href=https://wiki.jenkins-ci.org/display/JENKINS/Web+Method>Web Method</a>
*/
@Retention(RUNTIME)
@Target(PARAMETER)
@Documented
@InjectedParameter(GHEventPayload.PayloadHandler.class)
public @interface GHEventPayload {
class PayloadHandler extends AnnotationHandler<GHEventPayload> {
private static final Logger LOGGER = getLogger(PayloadHandler.class);
public static final String APPLICATION_JSON = "application/json";
public static final String FORM_URLENCODED = "application/x-www-form-urlencoded";
/**
* Registered handlers of specified content-types
*
* @see <a href=https://developer.github.com/webhooks/creating/#content-type>Developer manual</a>
*/
private static final Map<String, Function<StaplerRequest, String>> PAYLOAD_PROCESS =
ImmutableMap.<String, Function<StaplerRequest, String>>builder()
.put(APPLICATION_JSON, fromApplicationJson())
.put(FORM_URLENCODED, fromForm())
.build();
/**
* @param type string type expected
*
* @return String payload extracted from request or null on any problem
*/
@Override
public Object parse(StaplerRequest req, GHEventPayload a, Class type, String param) throws ServletException {
if (notNull(req, "Why StaplerRequest is null?").getHeader(GitHubWebHook.URL_VALIDATION_HEADER) != null) {
// if self test for custom hook url
return null;
}
String contentType = req.getContentType();
if (!PAYLOAD_PROCESS.containsKey(contentType)) {
LOGGER.error("Unknown content type {}", contentType);
return null;
}
String payload = PAYLOAD_PROCESS.get(contentType).apply(req);
LOGGER.trace("Payload {}", payload);
return payload;
}
/**
* used for application/x-www-form-urlencoded content-type
*
* @return function to extract payload from form request parameters
*/
protected static Function<StaplerRequest, String> fromForm() {
return new NullSafeFunction<StaplerRequest, String>() {
@Override
protected String applyNullSafe(@Nonnull StaplerRequest request) {
return request.getParameter("payload");
}
};
}
/**
* used for application/json content-type
*
* @return function to extract payload from body
*/
protected static Function<StaplerRequest, String> fromApplicationJson() {
return new NullSafeFunction<StaplerRequest, String>() {
@Override
protected String applyNullSafe(@Nonnull StaplerRequest request) {
try {
return IOUtils.toString(request.getInputStream(), Charsets.UTF_8);
} catch (IOException e) {
LOGGER.error("Can't get payload from request: {}", e.getMessage());
return null;
}
}
};
}
}
}