├── .github ├── CODEOWNERS ├── release-drafter.yml ├── dependabot.yml └── workflows │ ├── release-drafter.yml │ └── jenkins-security-scan.yml ├── .mvn ├── maven.config └── extensions.xml ├── src ├── main │ ├── webapp │ │ ├── help-authentication-parameter-name.html │ │ ├── help-authentication-parameter-value.html │ │ ├── help-partName.html │ │ ├── help-url.html │ │ ├── help-partFileName.html │ │ ├── help-quiet.html │ │ ├── help-httpMode.html │ │ ├── help-keyName.html │ │ ├── help-outputFile.html │ │ ├── help-httpProxy.html │ │ ├── help-timeout.html │ │ ├── help-consoleLogResponseBody.html │ │ ├── help-requestBody.html │ │ ├── help-acceptType.html │ │ ├── help-partBody.html │ │ ├── help-validResponseContent.html │ │ ├── help-contentType.html │ │ ├── help-ignoreSslErrors.html │ │ ├── help-multipartName.html │ │ ├── help-passBuildParameters.html │ │ ├── help-partUploadFile.html │ │ ├── help-wrapAsMultipart.html │ │ ├── help-partContentType.html │ │ ├── help-authentication.html │ │ ├── help-formData.html │ │ ├── use-system-properties.html │ │ ├── help-authentication-parameters.html │ │ ├── help-proxyAuthentication.html │ │ ├── help-uploadFile.html │ │ ├── help-responseHandle.html │ │ ├── help-validResponseCodes.html │ │ └── help-authentication-actions.html │ ├── resources │ │ ├── jenkins │ │ │ └── plugins │ │ │ │ └── http_request │ │ │ │ ├── HttpRequestStep │ │ │ │ ├── help-url.html │ │ │ │ ├── help-quiet.html │ │ │ │ ├── help-httpMode.html │ │ │ │ ├── help-outputFile.html │ │ │ │ ├── help-httpProxy.html │ │ │ │ ├── help-timeout.html │ │ │ │ ├── help-consoleLogResponseBody.html │ │ │ │ ├── help-requestBody.html │ │ │ │ ├── help-validResponseContent.html │ │ │ │ ├── help-acceptType.html │ │ │ │ ├── help-ignoreSslErrors.html │ │ │ │ ├── help-multipartName.html │ │ │ │ ├── help-contentType.html │ │ │ │ ├── help-wrapAsMultipart.html │ │ │ │ ├── help-authentication.html │ │ │ │ ├── help-useSystemProperties.html │ │ │ │ ├── help-uploadFile.html │ │ │ │ ├── help-responseHandle.html │ │ │ │ ├── help-validResponseCodes.html │ │ │ │ ├── help.html │ │ │ │ └── config.jelly │ │ │ │ ├── util │ │ │ │ ├── NameValuePair │ │ │ │ │ └── config.jelly │ │ │ │ ├── HttpRequestNameValuePair │ │ │ │ │ └── config.jelly │ │ │ │ └── HttpRequestFormDataPart │ │ │ │ │ └── config.jelly │ │ │ │ ├── HttpRequestGlobalConfig │ │ │ │ └── config.jelly │ │ │ │ └── HttpRequest │ │ │ │ └── config.jelly │ │ └── index.jelly │ └── java │ │ └── jenkins │ │ └── plugins │ │ └── http_request │ │ ├── ResponseHandle.java │ │ ├── util │ │ ├── HttpMkcol.java │ │ ├── HttpRequestValidation.java │ │ ├── BackWardCompatibleRedirectStrategy.java │ │ ├── HttpRequestNameValuePair.java │ │ ├── HttpRequestFormDataPart.java │ │ ├── RequestAction.java │ │ └── HttpClientUtil.java │ │ ├── HttpMode.java │ │ ├── auth │ │ ├── Authenticator.java │ │ ├── BasicDigestAuthentication.java │ │ ├── FormAuthentication.java │ │ ├── CredentialNtlmAuthentication.java │ │ ├── CredentialBasicAuthentication.java │ │ └── CertificateAuthentication.java │ │ ├── MimeType.java │ │ ├── HttpRequestGlobalConfig.java │ │ ├── ResponseContentSupplier.java │ │ └── HttpRequestStep.java ├── test │ ├── resources │ │ ├── jenkins │ │ │ └── plugins │ │ │ │ └── http_request │ │ │ │ ├── HttpRequestBackwardCompatibilityTest │ │ │ │ ├── defaultGlobalConfig │ │ │ │ │ ├── config.xml │ │ │ │ │ └── jenkins.plugins.http_request.HttpRequest.xml │ │ │ │ ├── populatedGlobalConfig │ │ │ │ │ ├── config.xml │ │ │ │ │ └── jenkins.plugins.http_request.HttpRequest.xml │ │ │ │ ├── oldConfigWithCustomHeadersShouldLoad │ │ │ │ │ ├── config.xml │ │ │ │ │ └── jobs │ │ │ │ │ │ └── old │ │ │ │ │ │ └── config.xml │ │ │ │ └── oldConfigWithoutCustomHeadersShouldLoad │ │ │ │ │ ├── config.xml │ │ │ │ │ └── jobs │ │ │ │ │ └── old │ │ │ │ │ └── config.xml │ │ │ │ ├── test.p12 │ │ │ │ ├── testTrusted.p12 │ │ │ │ └── README.txt │ │ └── testdata │ │ │ ├── small.zip │ │ │ └── readme.txt │ └── java │ │ ├── org │ │ └── apache │ │ │ └── hc │ │ │ └── client5 │ │ │ └── http │ │ │ └── impl │ │ │ └── classic │ │ │ └── HttpResponseAdapter.java │ │ └── jenkins │ │ └── plugins │ │ └── http_request │ │ ├── HttpRequestDescriptorImplTest.java │ │ ├── HttpRequestBackwardCompatibilityTest.java │ │ ├── HttpRequestStepRoundTripTest.java │ │ ├── HttpRequestRoundTripTest.java │ │ ├── HttpRequestTestBase.java │ │ ├── HttpRequestStepCredentialsTest.java │ │ └── Registers.java └── spotbugs │ └── excludesFilter.xml ├── .vscode └── settings.json ├── docs └── images │ ├── log.png │ ├── configure-http-request-global.png │ └── configure-http-request-build-step.png ├── .idea ├── codeStyles │ └── codeStyleConfig.xml ├── encodings.xml └── codeStyleSettings.xml ├── .settings └── org.eclipse.jdt.ui.prefs ├── .gitpod.yml ├── Jenkinsfile ├── .gitignore ├── LICENSE ├── pom.xml └── README.adoc /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/http-request-plugin-developers 2 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -------------------------------------------------------------------------------- /src/main/webapp/help-authentication-parameter-name.html: -------------------------------------------------------------------------------- 1 |
Name of parameter
2 | -------------------------------------------------------------------------------- /src/main/webapp/help-authentication-parameter-value.html: -------------------------------------------------------------------------------- 1 |
Value of parameter
2 | -------------------------------------------------------------------------------- /src/main/webapp/help-partName.html: -------------------------------------------------------------------------------- 1 |
2 | Name of this form-data Part. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-url.html: -------------------------------------------------------------------------------- 1 |
2 | Specify an URL to be requested. 3 |
4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "interactive" 3 | } -------------------------------------------------------------------------------- /docs/images/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/http-request-plugin/HEAD/docs/images/log.png -------------------------------------------------------------------------------- /src/main/webapp/help-partFileName.html: -------------------------------------------------------------------------------- 1 |
2 | Suggested filename of this Part. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-quiet.html: -------------------------------------------------------------------------------- 1 |
2 | This allows to turn off all logging output. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-httpMode.html: -------------------------------------------------------------------------------- 1 |
2 | The HTTP mode of the request such as 'get' or 'post'. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-keyName.html: -------------------------------------------------------------------------------- 1 |
2 | This is a unique name for this authentication process. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-outputFile.html: -------------------------------------------------------------------------------- 1 |
2 | Name of the file in which to write response data. 3 |
4 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/HttpRequestBackwardCompatibilityTest/defaultGlobalConfig/config.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/HttpRequestBackwardCompatibilityTest/populatedGlobalConfig/config.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-url.html: -------------------------------------------------------------------------------- 1 |
2 | Specify an URL to be requested. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-httpProxy.html: -------------------------------------------------------------------------------- 1 |
2 | Use a proxy to process the HTTP request. Example: http://mycorpproxy:80 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-timeout.html: -------------------------------------------------------------------------------- 1 |
2 | Specify a timeout value in seconds (default is 0 which implies no timeout). 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-consoleLogResponseBody.html: -------------------------------------------------------------------------------- 1 |
2 | This allows to turn off writing the response body to the log. 3 |
4 | -------------------------------------------------------------------------------- /src/test/resources/testdata/small.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/http-request-plugin/HEAD/src/test/resources/testdata/small.zip -------------------------------------------------------------------------------- /src/main/webapp/help-requestBody.html: -------------------------------------------------------------------------------- 1 |
2 |

The raw body of the request.

3 |

Parameters will be resolved.

4 |
5 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/HttpRequestBackwardCompatibilityTest/oldConfigWithCustomHeadersShouldLoad/config.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-quiet.html: -------------------------------------------------------------------------------- 1 |
2 | This allows to turn off all logging output. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-acceptType.html: -------------------------------------------------------------------------------- 1 |
2 | Add 'Accept: foo' HTTP request header where 'foo' is the HTTP content type to accept. 3 |
4 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/HttpRequestBackwardCompatibilityTest/oldConfigWithoutCustomHeadersShouldLoad/config.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/images/configure-http-request-global.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/http-request-plugin/HEAD/docs/images/configure-http-request-global.png -------------------------------------------------------------------------------- /src/main/webapp/help-partBody.html: -------------------------------------------------------------------------------- 1 |
2 | Textual content of this form-data Part. Don't forget to set the Content Type appropriately. 3 |
4 | -------------------------------------------------------------------------------- /docs/images/configure-http-request-build-step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/http-request-plugin/HEAD/docs/images/configure-http-request-build-step.png -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | This plugin sends a http request to an url with some parameters 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-httpMode.html: -------------------------------------------------------------------------------- 1 |
2 | The HTTP mode of the request such as 'GET' or 'POST'. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-outputFile.html: -------------------------------------------------------------------------------- 1 |
2 | Name of the file in which to write response data. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-validResponseContent.html: -------------------------------------------------------------------------------- 1 |
2 | If set response must contain this string to mark an execution as success.
3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-contentType.html: -------------------------------------------------------------------------------- 1 |
2 | Add 'Content-Type: foo' HTTP request header where 'foo' is the HTTP content type the request is using. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-ignoreSslErrors.html: -------------------------------------------------------------------------------- 1 |
2 | If set to true a request with HTTPS will trust in the certificate even when it is invalid or expired. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-multipartName.html: -------------------------------------------------------------------------------- 1 |
2 | Multipart entity name used in the Content-Disposition header in conjunction with the upload file path. 3 |
4 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/test.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/http-request-plugin/HEAD/src/test/resources/jenkins/plugins/http_request/test.p12 -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-httpProxy.html: -------------------------------------------------------------------------------- 1 |
2 | Use a proxy to process the HTTP request. Example: http://mycorpproxy:80 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-timeout.html: -------------------------------------------------------------------------------- 1 |
2 | Specify a timeout value in seconds (default is 0 which implies no timeout). 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-consoleLogResponseBody.html: -------------------------------------------------------------------------------- 1 |
2 | This allows to turn off writing the response body to the log. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-requestBody.html: -------------------------------------------------------------------------------- 1 |
2 |

The raw body of the request.

3 |

Parameters will be resolved.

4 |
5 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc 2 | 3 | _extends: .github 4 | tag-template: http_request-$NEXT_MINOR_VERSION 5 | -------------------------------------------------------------------------------- /src/main/webapp/help-passBuildParameters.html: -------------------------------------------------------------------------------- 1 |
2 |

Should build parameters be passed to the URL being called?

3 |

Ignored when request body is set.

4 |
5 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/testTrusted.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/http-request-plugin/HEAD/src/test/resources/jenkins/plugins/http_request/testTrusted.p12 -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/ResponseHandle.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | public enum ResponseHandle { 4 | NONE, 5 | LEAVE_OPEN, 6 | STRING 7 | } 8 | -------------------------------------------------------------------------------- /src/main/webapp/help-partUploadFile.html: -------------------------------------------------------------------------------- 1 |
2 | Path to the upload file, relative to build workspace or absolute path.

3 | The according content type should be set above. 4 |
5 | -------------------------------------------------------------------------------- /src/main/webapp/help-wrapAsMultipart.html: -------------------------------------------------------------------------------- 1 |
2 | If set to false an upload file will be set directly as body of the request and will not be 3 | wrapped as multipart/form-data. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-validResponseContent.html: -------------------------------------------------------------------------------- 1 |
2 | If set response must contain this string to mark an execution as success.
3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-acceptType.html: -------------------------------------------------------------------------------- 1 |
2 | Add Accept: foo HTTP request header where foo is the HTTP content type to accept. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-ignoreSslErrors.html: -------------------------------------------------------------------------------- 1 |
2 | If set to true, a request with HTTPS will trust in the certificate even when it is invalid or expired. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-partContentType.html: -------------------------------------------------------------------------------- 1 |
2 | Freetext Content Type of this form-data Part.

3 | Defaults to text/plain for text bodies, application/octet-stream or files. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-multipartName.html: -------------------------------------------------------------------------------- 1 |
2 | Multipart entity name used in the Content-Disposition header in conjunction with the upload file path. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-authentication.html: -------------------------------------------------------------------------------- 1 |
2 | Authentication that will be used before this request. 3 | Authentications are created in global configuration under a key name that is selected here. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-contentType.html: -------------------------------------------------------------------------------- 1 |
2 | Add Content-Type: foo HTTP request header where foo is the HTTP content type the request is using. 3 |
4 | -------------------------------------------------------------------------------- /src/main/webapp/help-formData.html: -------------------------------------------------------------------------------- 1 |
2 | Upload multiple entries as a multipart/form-data POST request.

3 | Each entry must have a Content Type, a Part Name, and either a Body or a FileName and Path. 4 |
5 | -------------------------------------------------------------------------------- /src/main/webapp/use-system-properties.html: -------------------------------------------------------------------------------- 1 |
2 | Use system properties to configure the client. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-wrapAsMultipart.html: -------------------------------------------------------------------------------- 1 |
2 | If set to false an upload file will be set directly as body of the request and will not be 3 | wrapped as multipart/form-data. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-authentication.html: -------------------------------------------------------------------------------- 1 |
2 | Authentication that will be used before this request. 3 | Authentications are created in global configuration under a key name that is selected here. 4 |
5 | -------------------------------------------------------------------------------- /src/main/webapp/help-authentication-parameters.html: -------------------------------------------------------------------------------- 1 |
2 | A sequence of parameter that will be send in this action request. e.g.
3 |
    4 |
  • j_username={username}
  • 5 |
  • j_password={pass}
  • 6 |
7 |
8 | -------------------------------------------------------------------------------- /src/main/webapp/help-proxyAuthentication.html: -------------------------------------------------------------------------------- 1 |
2 | Proxy authentication that will be used before this request - only applicable if Http Proxy is set. 3 | Authentications are created in global configuration under a key name that is selected here. 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-useSystemProperties.html: -------------------------------------------------------------------------------- 1 |
2 | Use system properties to configure the client. 3 |
4 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.ui.prefs: -------------------------------------------------------------------------------- 1 | cleanup_settings_version=2 2 | eclipse.preferences.version=1 3 | org.eclipse.jdt.ui.ignorelowercasenames=true 4 | org.eclipse.jdt.ui.importorder=\#;groovy;java;javax;net;org;com;;jenkins.plugins.http_request; 5 | org.eclipse.jdt.ui.ondemandthreshold=5000 6 | org.eclipse.jdt.ui.staticondemandthreshold=5000 -------------------------------------------------------------------------------- /src/main/webapp/help-uploadFile.html: -------------------------------------------------------------------------------- 1 |
2 | Path to the upload file, relative to build workspace or absolute path.

3 | Can be used to upload a file as multipart/form-data POST request. 4 | The according content type should be set above, defaults to application/octet-stream otherwise. 5 |
6 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: mvn clean verify 3 | 4 | vscode: 5 | extensions: 6 | - bierner.markdown-preview-github-styles 7 | - vscjava.vscode-java-pack 8 | - redhat.java 9 | - vscjava.vscode-java-debug 10 | - vscjava.vscode-java-dependency 11 | - vscjava.vscode-java-test 12 | - vscjava.vscode-maven 13 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-uploadFile.html: -------------------------------------------------------------------------------- 1 |
2 | Path to the upload file, relative to build workspace or absolute path.

3 | Can be used to upload a file as multipart/form-data POST request. 4 | The according content type should be set above, defaults to application/octet-stream otherwise. 5 |
6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: maven 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: monthly 13 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin( 6 | forkCount: '1C', // Run parallel tests on ci.jenkins.io for lower costs, faster feedback 7 | useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests 8 | configurations: [ 9 | [platform: 'linux', jdk: 25], 10 | [platform: 'windows', jdk: 21], 11 | ]) 12 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.13 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/main/webapp/help-responseHandle.html: -------------------------------------------------------------------------------- 1 |
2 | How response should be handled.
3 | STRING(default) will transform the response in string.
4 | NONE will not read the response content and will close the response after the job execution.
5 | LEAVE_OPEN will not read the response content but leaves an open input stream to be read outside the job execution. 6 | Using LEAVE_OPEN it is your responsibility to close it after use. 'response.close();'
7 |
8 | -------------------------------------------------------------------------------- /src/test/resources/testdata/readme.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor 2 | incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 3 | nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 4 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore 5 | eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt 6 | in culpa qui officia deserunt mollit anim id est laborum. 7 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/util/NameValuePair/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /src/main/webapp/help-validResponseCodes.html: -------------------------------------------------------------------------------- 1 |
2 | Configure response code to mark an execution as success.
3 | You can configure simple code such as "200" or multiple codes separeted by comma(',') e.g. "200,404,500"
4 | Interval of codes should be in format From:To e.g. "100:399".
5 | 6 | The default (as if empty) is to fail to 4xx and 5xx. That means success from 100 to 399 "100:399".
7 | To ignore any response code use "100:599". 8 |
9 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-responseHandle.html: -------------------------------------------------------------------------------- 1 |
2 | How response should be handled.
3 | STRING(default) will transform the response in string.
4 | NONE will not read the response content and will close the response after the job execution.
5 | LEAVE_OPEN will not read the response content but leaves an open input stream to be read outside the job execution. 6 | Using LEAVE_OPEN it is your responsibility to close it after use. 'response.close();'
7 |
8 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help-validResponseCodes.html: -------------------------------------------------------------------------------- 1 |
2 | Configure response code to mark an execution as success.
3 | You can configure simple code such as "200" or multiple codes separeted by comma(',') e.g. "200,404,500"
4 | Interval of codes should be in format From:To e.g. "100:399".
5 | 6 | The default (as if empty) is to fail to 4xx and 5xx. That means success from 100 to 399 "100:399".
7 | To ignore any response code use "100:599". 8 |
9 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/util/HttpMkcol.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.util; 2 | 3 | import java.io.Serial; 4 | import java.net.URI; 5 | 6 | import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; 7 | 8 | public class HttpMkcol extends HttpUriRequestBase { 9 | 10 | @Serial 11 | private static final long serialVersionUID = 1L; 12 | 13 | public static final String METHOD_NAME = "MKCOL"; 14 | 15 | public HttpMkcol(final String uri) { 16 | super(METHOD_NAME, URI.create(uri)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Automates creation of Release Drafts using Release Drafter 2 | # More Info: https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Drafts your next Release notes as Pull Requests are merged into the default branch 14 | - uses: release-drafter/release-drafter@v6 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /src/main/webapp/help-authentication-actions.html: -------------------------------------------------------------------------------- 1 |
2 | A sequence of actions used in the process of authentication.
3 | e.g. Using FormAuthentication 4 |
    5 |
  • Action 1 - Get to url form
  • 6 |
  • Action 2 - Post to {url}/j_security_check whit params: 7 |
      8 |
    • j_username={username}
    • 9 |
    • j_password={pass}
    • 10 |
    11 |
  • 12 |
  • Action 3 - Get to form
  • 13 |
14 | Then job execution do a request of job 15 |
16 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/HttpRequestBackwardCompatibilityTest/defaultGlobalConfig/jenkins.plugins.http_request.HttpRequest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | POST 4 | 5 | 6 | true 7 | true 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/HttpMode.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import hudson.util.ListBoxModel; 4 | 5 | /** 6 | * @author Janario Oliveira 7 | */ 8 | public enum HttpMode { 9 | GET, 10 | HEAD, 11 | POST, 12 | PUT, 13 | DELETE, 14 | OPTIONS, 15 | PATCH, 16 | MKCOL; 17 | 18 | public static ListBoxModel getFillItems() { 19 | ListBoxModel items = new ListBoxModel(); 20 | for (HttpMode httpMode : values()) { 21 | items.add(httpMode.name()); 22 | } 23 | return items; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/org/apache/hc/client5/http/impl/classic/HttpResponseAdapter.java: -------------------------------------------------------------------------------- 1 | package org.apache.hc.client5.http.impl.classic; 2 | 3 | import org.apache.hc.core5.http.ClassicHttpResponse; 4 | 5 | public final class HttpResponseAdapter { 6 | 7 | /** 8 | * Wraps {@link CloseableHttpResponse#adapt(ClassicHttpResponse)}. 9 | * @param response the ClassicHttpResponse 10 | * @return adapted CloseableHttpResponse 11 | */ 12 | public static CloseableHttpResponse adapt(final ClassicHttpResponse response) { 13 | return CloseableHttpResponse.adapt(response); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/util/HttpRequestValidation.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.util; 2 | 3 | import java.net.MalformedURLException; 4 | import java.net.URL; 5 | 6 | import hudson.util.FormValidation; 7 | 8 | /** 9 | * @author Janario Oliveira 10 | */ 11 | public class HttpRequestValidation { 12 | 13 | public static FormValidation checkUrl(String value) { 14 | try { 15 | new URL(value); 16 | return FormValidation.ok(); 17 | } catch (MalformedURLException ex) { 18 | return FormValidation.error("Invalid url"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/util/HttpRequestNameValuePair/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Backups created by gedit 2 | *~ 3 | 4 | #Jenkins work 5 | work/* 6 | work_copy/* 7 | 8 | # Vim files 9 | Session.vim 10 | *.swp 11 | 12 | # Eclipse 13 | .project 14 | .classpath 15 | .settings/* 16 | !.settings/org.eclipse.jdt.core.prefs 17 | !.settings/org.eclipse.jdt.ui.prefs 18 | .springBeans 19 | .metadata 20 | .externalToolBuilders 21 | test-output 22 | 23 | # Maven 24 | target 25 | pom.xml.tag 26 | pom.xml.releaseBackup 27 | pom.xml.versionsBackup 28 | pom.xml.next 29 | release.properties 30 | 31 | # Idea 32 | *.iml 33 | **/.idea/* 34 | !.idea/codeStyleSettings.xml 35 | !.idea/codeStyles/codeStyleConfig.xml 36 | !.idea/codeStyles/Project.xml 37 | !.idea/encodings.xml -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | # Jenkins Security Scan 2 | # For more information, see: https://www.jenkins.io/doc/developer/security/scan/ 3 | 4 | name: Jenkins Security Scan 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | pull_request: 11 | types: [opened, synchronize, reopened] 12 | workflow_dispatch: 13 | 14 | permissions: 15 | security-events: write 16 | contents: read 17 | actions: read 18 | 19 | jobs: 20 | security-scan: 21 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 22 | with: 23 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 24 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 25 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/auth/Authenticator.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.auth; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintStream; 5 | import java.io.Serializable; 6 | 7 | import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; 8 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 9 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 10 | import org.apache.hc.client5.http.protocol.HttpClientContext; 11 | 12 | /** 13 | * @author Janario Oliveira 14 | */ 15 | public interface Authenticator extends Serializable { 16 | 17 | String getKeyName(); 18 | 19 | CloseableHttpClient authenticate(HttpClientBuilder clientBuilder, HttpClientContext context, HttpUriRequestBase requestBase, 20 | PrintStream logger) throws IOException, InterruptedException; 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/HttpRequestBackwardCompatibilityTest/oldConfigWithoutCustomHeadersShouldLoad/jobs/old/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | false 14 | 15 | 16 | url 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/util/HttpRequestFormDataPart/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2012-, Janario Oliveira, and a number of other of contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/spotbugs/excludesFilter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/help.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Performs an HTTP request, and returns a response object. 4 |

Usage example:

5 |
 6 |         def response = httpRequest 'http://localhost:8080/jenkins/api/json?pretty=true'
 7 |         println("Status: "+response.status)
 8 |         println("Content: "+response.content)
 9 |         
10 |

If Jenkins restarts after the HTTP request is made, but before the HTTP response is received, the HTTP request fails.

11 |

validResponseCodes is a comma-separated string of single values or from:to ranges. For example '200' to accept only 200 or '201,301:303' to accept 201 as well as the range from 301 to 303.

12 |

13 |

14 | The methods of the response object are: 15 |

    16 |
  • 17 | String getContent(): The HTTP Response entity. This means the text of the response without the headers. If the response does not contain an entity, getContent() returns null. 18 |
  • 19 |
  • 20 | int getStatus(): The HTTP status code. 21 |
  • 22 |
23 |

24 |
25 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/http_request/HttpRequestDescriptorImplTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.List; 6 | import java.util.stream.IntStream; 7 | 8 | import org.junit.jupiter.api.Test; 9 | 10 | /** 11 | * @author Martin Mosegaard Amdisen 12 | */ 13 | class HttpRequestDescriptorImplTest { 14 | 15 | private static final List DEFAULT_VALID_RESPONSE_CODES_RANGE = streamToList(IntStream.rangeClosed(100, 399)); 16 | 17 | @Test 18 | void parseToRangeShouldHandleEmptyString() { 19 | String value = ""; 20 | List ranges = HttpRequest.DescriptorImpl.parseToRange(value); 21 | assertEquals(1, ranges.size()); 22 | assertEquals(DEFAULT_VALID_RESPONSE_CODES_RANGE, streamToList(ranges.get(0))); 23 | } 24 | 25 | @Test 26 | void parseToRangeShouldHandleNull() { 27 | String value = null; 28 | List ranges = HttpRequest.DescriptorImpl.parseToRange(value); 29 | assertEquals(1, ranges.size()); 30 | assertEquals(DEFAULT_VALID_RESPONSE_CODES_RANGE, streamToList(ranges.get(0))); 31 | } 32 | 33 | private static List streamToList(IntStream stream) { 34 | return stream.boxed().toList(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/HttpRequestBackwardCompatibilityTest/oldConfigWithCustomHeadersShouldLoad/jobs/old/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | false 14 | 15 | 16 | url 17 | POST 18 | NOT_SET 19 | NOT_SET 20 | 21 | true 22 | true 23 | 24 | 25 | h1 26 | v1 27 | 28 | 29 | 100:399 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/MimeType.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import org.apache.hc.core5.http.ContentType; 4 | 5 | import hudson.util.ListBoxModel; 6 | 7 | /** 8 | * @author James Chapman 9 | */ 10 | public enum MimeType { 11 | 12 | NOT_SET(null), 13 | TEXT_HTML(ContentType.TEXT_HTML), 14 | TEXT_PLAIN(ContentType.TEXT_PLAIN), 15 | APPLICATION_FORM(ContentType.APPLICATION_FORM_URLENCODED), 16 | APPLICATION_FORM_DATA(ContentType.MULTIPART_FORM_DATA), 17 | APPLICATION_JSON(ContentType.create("application/json")), 18 | APPLICATION_JSON_UTF8(ContentType.APPLICATION_JSON), 19 | APPLICATION_TAR(ContentType.create("application/x-tar")), 20 | APPLICATION_ZIP(ContentType.create("application/zip")), 21 | APPLICATION_OCTETSTREAM(ContentType.APPLICATION_OCTET_STREAM); 22 | 23 | private final ContentType contentType; 24 | 25 | MimeType(ContentType contentType) { 26 | this.contentType = contentType; 27 | } 28 | 29 | public String getValue() { 30 | return contentType.getMimeType(); 31 | } 32 | 33 | public ContentType getContentType() { 34 | return contentType; 35 | } 36 | 37 | public static ListBoxModel getContentTypeFillItems() { 38 | ListBoxModel items = new ListBoxModel(); 39 | for (MimeType mimeType : values()) { 40 | items.add(mimeType.name()); 41 | } 42 | return items; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/HttpRequestBackwardCompatibilityTest/populatedGlobalConfig/jenkins.plugins.http_request.HttpRequest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | POST 4 | 5 | 6 | k1 7 | u1 8 | p1 9 | 10 | 11 | k2 12 | u2 13 | p2 14 | 15 | 16 | 17 | 18 | k3 19 | 20 | 21 | http://localhost1 22 | GET 23 | 24 | 25 | name1 26 | value1 27 | 28 | 29 | 30 | 31 | 32 | 33 | true 34 | true 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/util/BackWardCompatibleRedirectStrategy.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.util; 2 | 3 | import org.apache.hc.client5.http.classic.methods.HttpGet; 4 | import org.apache.hc.client5.http.classic.methods.HttpHead; 5 | import org.apache.hc.client5.http.impl.DefaultRedirectStrategy; 6 | import org.apache.hc.core5.http.Header; 7 | import org.apache.hc.core5.http.HttpHeaders; 8 | import org.apache.hc.core5.http.HttpRequest; 9 | import org.apache.hc.core5.http.HttpResponse; 10 | import org.apache.hc.core5.http.HttpStatus; 11 | import org.apache.hc.core5.http.protocol.HttpContext; 12 | 13 | public class BackWardCompatibleRedirectStrategy extends DefaultRedirectStrategy { 14 | 15 | private static final String[] REDIRECT_METHODS = new String[]{HttpGet.METHOD_NAME, HttpHead.METHOD_NAME}; 16 | 17 | @Override 18 | public boolean isRedirected(final HttpRequest request, final HttpResponse response, final HttpContext context) { 19 | if (!response.containsHeader(HttpHeaders.LOCATION)) { 20 | return false; 21 | } 22 | 23 | final int statusCode = response.getCode(); 24 | final String method = request.getMethod(); 25 | final Header locationHeader = response.getFirstHeader("location"); 26 | return switch (statusCode) { 27 | case HttpStatus.SC_MOVED_TEMPORARILY -> 28 | isRedirectable(method) && locationHeader != null; 29 | case HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_TEMPORARY_REDIRECT, 30 | HttpStatus.SC_PERMANENT_REDIRECT -> isRedirectable(method); 31 | case HttpStatus.SC_SEE_OTHER -> true; 32 | default -> false; 33 | }; 34 | } 35 | 36 | protected boolean isRedirectable(final String method) { 37 | for (final String m : REDIRECT_METHODS) { 38 | if (m.equalsIgnoreCase(method)) { 39 | return true; 40 | } 41 | } 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/util/HttpRequestNameValuePair.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.util; 2 | 3 | import java.io.Serial; 4 | import java.io.Serializable; 5 | 6 | import org.apache.hc.core5.http.NameValuePair; 7 | import org.kohsuke.stapler.DataBoundConstructor; 8 | import org.kohsuke.stapler.QueryParameter; 9 | 10 | import edu.umd.cs.findbugs.annotations.NonNull; 11 | import hudson.Extension; 12 | import hudson.model.AbstractDescribableImpl; 13 | import hudson.model.Descriptor; 14 | import hudson.util.FormValidation; 15 | 16 | /** 17 | * @author Janario Oliveira 18 | */ 19 | public class HttpRequestNameValuePair extends AbstractDescribableImpl 20 | implements NameValuePair, Serializable { 21 | 22 | @Serial 23 | private static final long serialVersionUID = -5179602567301232134L; 24 | private final String name; 25 | private final String value; 26 | private final boolean maskValue; 27 | 28 | @DataBoundConstructor 29 | public HttpRequestNameValuePair(String name, String value, boolean maskValue) { 30 | this.name = name; 31 | this.value = value; 32 | this.maskValue = maskValue; 33 | } 34 | 35 | public HttpRequestNameValuePair(String name, String value) { 36 | this(name, value, false); 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public String getValue() { 44 | return value; 45 | } 46 | 47 | public boolean getMaskValue() { 48 | return maskValue; 49 | } 50 | 51 | @Extension 52 | public static class NameValueParamDescriptor extends Descriptor { 53 | 54 | @NonNull 55 | @Override 56 | public String getDisplayName() { 57 | return "Name Value Param"; 58 | } 59 | 60 | public FormValidation doCheckName(@QueryParameter String value) { 61 | return FormValidation.validateRequired(value); 62 | } 63 | 64 | public FormValidation doCheckValue(@QueryParameter String value) { 65 | return FormValidation.validateRequired(value); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/util/HttpRequestFormDataPart.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.util; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import hudson.FilePath; 6 | import hudson.model.AbstractDescribableImpl; 7 | import hudson.model.Descriptor; 8 | import hudson.util.FormValidation; 9 | 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | import org.kohsuke.stapler.QueryParameter; 12 | 13 | import java.io.Serial; 14 | import java.io.Serializable; 15 | 16 | public class HttpRequestFormDataPart extends AbstractDescribableImpl 17 | implements Serializable { 18 | 19 | @Serial 20 | private static final long serialVersionUID = 1L; 21 | private final String contentType; 22 | private final String body; 23 | private final String name; 24 | private final String fileName; 25 | private final String uploadFile; 26 | private FilePath resolvedUploadFile; 27 | 28 | @DataBoundConstructor 29 | public HttpRequestFormDataPart( 30 | final String uploadFile, 31 | final String name, 32 | final String fileName, 33 | final String contentType, 34 | final String body) { 35 | this.contentType = contentType; 36 | this.body = body; 37 | this.name = name; 38 | this.fileName = fileName; 39 | this.uploadFile = uploadFile; 40 | } 41 | 42 | public String getBody() { 43 | return body; 44 | } 45 | 46 | public String getContentType() { 47 | return contentType; 48 | } 49 | 50 | public String getFileName() { 51 | return fileName; 52 | } 53 | 54 | public String getName() { 55 | return name; 56 | } 57 | 58 | public String getUploadFile() { 59 | return uploadFile; 60 | } 61 | 62 | public FilePath getResolvedUploadFile() { 63 | return resolvedUploadFile; 64 | } 65 | 66 | public void setResolvedUploadFile(FilePath resolvedUploadFile) { 67 | this.resolvedUploadFile = resolvedUploadFile; 68 | } 69 | 70 | @Extension 71 | public static class FormDataDescriptor extends Descriptor { 72 | @NonNull 73 | @Override 74 | public String getDisplayName() { 75 | return "Multipart Form Data Entry"; 76 | } 77 | 78 | public FormValidation doCheckName(@QueryParameter String value) { 79 | return FormValidation.validateRequired(value); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/util/RequestAction.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.util; 2 | 3 | import java.io.Serial; 4 | import java.io.Serializable; 5 | import java.net.URL; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | import org.kohsuke.stapler.QueryParameter; 12 | 13 | import edu.umd.cs.findbugs.annotations.NonNull; 14 | import hudson.Extension; 15 | import hudson.model.AbstractDescribableImpl; 16 | import hudson.model.Descriptor; 17 | import hudson.util.FormValidation; 18 | import hudson.util.ListBoxModel; 19 | 20 | import jenkins.plugins.http_request.HttpMode; 21 | 22 | /** 23 | * @author Janario Oliveira 24 | */ 25 | public class RequestAction extends AbstractDescribableImpl implements Serializable { 26 | 27 | @Serial 28 | private static final long serialVersionUID = 7846277147434838878L; 29 | private final URL url; 30 | private final HttpMode mode; 31 | private final String requestBody; 32 | private final List params; 33 | private final List headers; 34 | 35 | @DataBoundConstructor 36 | public RequestAction(URL url, HttpMode mode, String requestBody, List params) { 37 | this(url, mode, requestBody, params, null); 38 | } 39 | 40 | public RequestAction(URL url, HttpMode mode, String requestBody, List params, List headers) { 41 | this.url = url; 42 | this.mode = mode; 43 | this.requestBody = requestBody; 44 | this.params = params == null ? new ArrayList<>() : params; 45 | this.headers = headers == null ? new ArrayList<>() : headers; 46 | } 47 | 48 | public URL getUrl() { 49 | return url; 50 | } 51 | 52 | public HttpMode getMode() { 53 | return mode; 54 | } 55 | 56 | public List getParams() { 57 | return Collections.unmodifiableList(params); 58 | } 59 | 60 | public List getHeaders() { 61 | return Collections.unmodifiableList(headers); 62 | } 63 | 64 | public String getRequestBody() { 65 | return requestBody; 66 | } 67 | 68 | @Extension 69 | public static class ActionFormAuthenticationDescriptor extends Descriptor { 70 | 71 | @NonNull 72 | @Override 73 | public String getDisplayName() { 74 | return "Action Form Authentication"; 75 | } 76 | 77 | public FormValidation doCheckUrl(@QueryParameter String value) { 78 | return HttpRequestValidation.checkUrl(value); 79 | } 80 | 81 | public FormValidation doCheckTimeout(@QueryParameter String timeout) { 82 | try { 83 | Integer.parseInt(timeout); 84 | return FormValidation.ok(); 85 | } catch (NumberFormatException e) { 86 | return FormValidation.error("Not a number"); 87 | } 88 | } 89 | 90 | public ListBoxModel doFillModeItems() { 91 | return HttpMode.getFillItems(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/auth/BasicDigestAuthentication.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.auth; 2 | 3 | import java.io.PrintStream; 4 | import java.io.Serial; 5 | import java.net.URISyntaxException; 6 | 7 | import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; 8 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 9 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 10 | import org.apache.hc.client5.http.protocol.HttpClientContext; 11 | import org.apache.hc.client5.http.utils.URIUtils; 12 | import org.kohsuke.stapler.DataBoundConstructor; 13 | import org.kohsuke.stapler.QueryParameter; 14 | 15 | import edu.umd.cs.findbugs.annotations.NonNull; 16 | import hudson.Extension; 17 | import hudson.model.AbstractDescribableImpl; 18 | import hudson.model.Descriptor; 19 | import hudson.util.FormValidation; 20 | 21 | import jenkins.plugins.http_request.HttpRequestGlobalConfig; 22 | 23 | /** 24 | * @author Janario Oliveira 25 | * @deprecated use Jenkins credentials, marked to remove in 1.8.19 26 | */ 27 | @Deprecated 28 | public class BasicDigestAuthentication extends AbstractDescribableImpl 29 | implements Authenticator { 30 | @Serial 31 | private static final long serialVersionUID = 4818288270720177069L; 32 | 33 | private final String keyName; 34 | private final String userName; 35 | private final String password; 36 | 37 | @DataBoundConstructor 38 | public BasicDigestAuthentication(String keyName, String userName, 39 | String password) { 40 | this.keyName = keyName; 41 | this.userName = userName; 42 | this.password = password; 43 | } 44 | 45 | public String getKeyName() { 46 | return keyName; 47 | } 48 | 49 | public String getUserName() { 50 | return userName; 51 | } 52 | 53 | public String getPassword() { 54 | return password; 55 | } 56 | 57 | @Override 58 | public CloseableHttpClient authenticate(HttpClientBuilder clientBuilder, HttpClientContext context, 59 | HttpUriRequestBase requestBase, PrintStream logger) { 60 | try { 61 | CredentialBasicAuthentication.auth(clientBuilder, context, URIUtils.extractHost(requestBase.getUri()), userName, password, null); 62 | return clientBuilder.build(); 63 | } catch (URISyntaxException ex) { 64 | throw new IllegalArgumentException(ex); 65 | } 66 | } 67 | 68 | @Extension 69 | public static class BasicDigestAuthenticationDescriptor extends Descriptor { 70 | 71 | public FormValidation doCheckKeyName(@QueryParameter String value) { 72 | return HttpRequestGlobalConfig.validateKeyName(value); 73 | } 74 | 75 | public FormValidation doCheckUserName(@QueryParameter String value) { 76 | return FormValidation.validateRequired(value); 77 | } 78 | 79 | public FormValidation doCheckPassword(@QueryParameter String value) { 80 | return FormValidation.validateRequired(value); 81 | } 82 | 83 | @NonNull 84 | @Override 85 | public String getDisplayName() { 86 | return "Basic/Digest Authentication"; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/auth/FormAuthentication.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.auth; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintStream; 5 | import java.io.Serial; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; 11 | import org.apache.hc.client5.http.protocol.HttpClientContext; 12 | import org.apache.hc.core5.http.HttpResponse; 13 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 14 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 15 | import org.kohsuke.stapler.DataBoundConstructor; 16 | import org.kohsuke.stapler.QueryParameter; 17 | 18 | import edu.umd.cs.findbugs.annotations.NonNull; 19 | import hudson.Extension; 20 | import hudson.model.AbstractDescribableImpl; 21 | import hudson.model.Descriptor; 22 | import hudson.util.FormValidation; 23 | 24 | import jenkins.plugins.http_request.HttpRequestGlobalConfig; 25 | import jenkins.plugins.http_request.util.HttpClientUtil; 26 | import jenkins.plugins.http_request.util.RequestAction; 27 | 28 | /** 29 | * @author Janario Oliveira 30 | */ 31 | public class FormAuthentication extends AbstractDescribableImpl 32 | implements Authenticator { 33 | 34 | @Serial 35 | private static final long serialVersionUID = -4370238820437831639L; 36 | private final String keyName; 37 | private final List actions; 38 | 39 | @DataBoundConstructor 40 | public FormAuthentication(String keyName, List actions) { 41 | this.keyName = keyName; 42 | this.actions = actions == null ? new ArrayList<>() : actions; 43 | } 44 | 45 | public String getKeyName() { 46 | return keyName; 47 | } 48 | 49 | public List getActions() { 50 | return Collections.unmodifiableList(actions); 51 | } 52 | 53 | @Override 54 | public CloseableHttpClient authenticate(HttpClientBuilder clientBuilder, HttpClientContext context, 55 | HttpUriRequestBase requestBase, PrintStream logger) throws IOException { 56 | CloseableHttpClient client = clientBuilder.build(); 57 | final HttpClientUtil clientUtil = new HttpClientUtil(); 58 | for (RequestAction requestAction : actions) { 59 | final HttpUriRequestBase method = clientUtil.createRequestBase(requestAction); 60 | 61 | final HttpResponse execute = clientUtil.execute(client, context, method, logger); 62 | //from 400(client error) to 599(server error) 63 | if ((execute.getCode() >= 400 && execute.getCode() <= 599)) { 64 | throw new IllegalStateException("Error doing authentication"); 65 | } 66 | } 67 | return client; 68 | } 69 | 70 | @Extension 71 | public static class FormAuthenticationDescriptor extends Descriptor { 72 | 73 | public FormValidation doCheckKeyName(@QueryParameter String value) { 74 | return HttpRequestGlobalConfig.validateKeyName(value); 75 | } 76 | 77 | @NonNull 78 | @Override 79 | public String getDisplayName() { 80 | return "Form Authentication"; 81 | } 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestGlobalConfig/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 |
36 |
37 | 38 |
39 | 40 |
41 |
42 |
43 |
44 |
45 | 46 | 47 |
48 | 49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/auth/CredentialNtlmAuthentication.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.auth; 2 | 3 | import java.io.PrintStream; 4 | import java.net.URISyntaxException; 5 | import java.util.List; 6 | 7 | import org.apache.hc.client5.http.auth.AuthSchemeFactory; 8 | import org.apache.hc.client5.http.auth.StandardAuthScheme; 9 | import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; 10 | import org.apache.hc.client5.http.config.RequestConfig; 11 | import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; 12 | import org.apache.hc.client5.http.auth.AuthScope; 13 | import org.apache.hc.client5.http.auth.NTCredentials; 14 | import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory; 15 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 16 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 17 | import org.apache.hc.client5.http.protocol.HttpClientContext; 18 | import org.apache.hc.core5.http.config.Lookup; 19 | import org.apache.hc.core5.http.config.RegistryBuilder; 20 | 21 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 22 | 23 | /** 24 | * @author Daniel Torrescusa 25 | */ 26 | public class CredentialNtlmAuthentication implements Authenticator { 27 | 28 | private final StandardUsernamePasswordCredentials credential; 29 | private final String username; 30 | private final String domain; 31 | 32 | public CredentialNtlmAuthentication(StandardUsernamePasswordCredentials credential) { 33 | this.credential = credential; 34 | 35 | String[] split = credential.getUsername().split("\\\\"); 36 | Integer pieces = split.length; 37 | 38 | if (pieces.equals(2)) { 39 | this.username = split[1]; 40 | this.domain = split[0]; 41 | } else if (pieces.equals(1)) { 42 | this.username = split[0]; 43 | this.domain = null; 44 | } else { 45 | throw new IllegalStateException("Username contains more than one \\"); 46 | } 47 | 48 | } 49 | 50 | @Override 51 | public String getKeyName() { 52 | return credential.getId(); 53 | } 54 | 55 | @Override 56 | public CloseableHttpClient authenticate(HttpClientBuilder clientBuilder, HttpClientContext context, HttpUriRequestBase requestBase, PrintStream logger) { 57 | return auth(clientBuilder, context, requestBase, 58 | username, credential.getPassword().getPlainText(), domain); 59 | } 60 | 61 | static CloseableHttpClient auth(HttpClientBuilder clientBuilder, HttpClientContext context, HttpUriRequestBase requestBase, 62 | String username, String password, String domain) { 63 | try { 64 | BasicCredentialsProvider provider = new BasicCredentialsProvider(); 65 | provider.setCredentials( 66 | new AuthScope(requestBase.getUri().getHost(), requestBase.getUri().getPort()), 67 | new NTCredentials(username, password.toCharArray(), requestBase.getUri().getHost(), domain)); 68 | 69 | // register NTLM authentication support explicitly 70 | Lookup authRegistry = RegistryBuilder.create() 71 | .register(StandardAuthScheme.NTLM, NTLMSchemeFactory.INSTANCE) 72 | .build(); 73 | RequestConfig config = RequestConfig.custom().setTargetPreferredAuthSchemes(List.of(StandardAuthScheme.NTLM)) 74 | .build(); 75 | 76 | clientBuilder 77 | .setDefaultRequestConfig(config) 78 | .setDefaultCredentialsProvider(provider) 79 | .setDefaultAuthSchemeRegistry(authRegistry); 80 | 81 | context.setRequestConfig(config); 82 | context.setCredentialsProvider(provider); 83 | context.setAuthSchemeRegistry(authRegistry); 84 | 85 | return clientBuilder.build(); 86 | } catch (URISyntaxException ex) { 87 | throw new IllegalArgumentException(ex); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequestStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main/resources/jenkins/plugins/http_request/HttpRequest/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/http_request/HttpRequestBackwardCompatibilityTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.util.Collections; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | 10 | import org.junit.jupiter.api.Test; 11 | import org.jvnet.hudson.test.JenkinsRule; 12 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 13 | import org.jvnet.hudson.test.recipes.LocalData; 14 | 15 | import hudson.model.FreeStyleProject; 16 | import hudson.tasks.Builder; 17 | 18 | import jenkins.plugins.http_request.auth.FormAuthentication; 19 | import jenkins.plugins.http_request.util.HttpRequestNameValuePair; 20 | import jenkins.plugins.http_request.util.RequestAction; 21 | 22 | /** 23 | * @author Martin d'Anjou 24 | */ 25 | @WithJenkins 26 | class HttpRequestBackwardCompatibilityTest { 27 | 28 | @LocalData 29 | @Test 30 | void defaultGlobalConfig(JenkinsRule j) { 31 | // Test that config from 1.8.6 can be loaded 32 | HttpRequestGlobalConfig cfg = HttpRequestGlobalConfig.get(); 33 | assertEquals(Collections.emptyList(), cfg.getFormAuthentications()); 34 | assertEquals("jenkins.plugins.http_request.HttpRequest.xml", cfg.getConfigFile().getFile().getName()); 35 | } 36 | 37 | @LocalData 38 | @Test 39 | void populatedGlobalConfig(JenkinsRule j) { 40 | // Test that global config from 1.8.6 can be loaded 41 | // Specifically tests the HttpRequestGlobalConfig.xStreamCompatibility() method 42 | // and the HttpRequestGlobalConfig.getConfigFile() method 43 | HttpRequestGlobalConfig cfg = HttpRequestGlobalConfig.get(); 44 | 45 | List fas = cfg.getFormAuthentications(); 46 | assertEquals(1,fas.size()); 47 | 48 | FormAuthentication fa = fas.iterator().next(); 49 | assertEquals("k3", fa.getKeyName()); 50 | List ras = fa.getActions(); 51 | assertEquals(1,ras.size()); 52 | 53 | RequestAction ra = ras.iterator().next(); 54 | assertEquals("http://localhost1",ra.getUrl().toString()); 55 | assertEquals("GET",ra.getMode().toString()); 56 | List nvps = ra.getParams(); 57 | assertEquals(1,nvps.size()); 58 | 59 | HttpRequestNameValuePair nvp = nvps.iterator().next(); 60 | assertEquals("name1",nvp.getName()); 61 | assertEquals("value1",nvp.getValue()); 62 | } 63 | 64 | @LocalData 65 | @Test 66 | void oldConfigWithoutCustomHeadersShouldLoad(JenkinsRule j) { 67 | // Test that a job config from 1.8.6 can be loaded 68 | // Specifically tests the HttpRequest.readResolve() method 69 | FreeStyleProject p = (FreeStyleProject) j.getInstance().getItem("old"); 70 | 71 | List builders = p.getBuilders(); 72 | 73 | HttpRequest httpRequest = (HttpRequest) builders.get(0); 74 | assertEquals("url", httpRequest.getUrl()); 75 | assertNotNull(httpRequest.getCustomHeaders()); 76 | assertNotNull(httpRequest.getValidResponseCodes()); 77 | assertEquals("100:399", httpRequest.getValidResponseCodes()); 78 | } 79 | 80 | @LocalData 81 | @Test 82 | void oldConfigWithCustomHeadersShouldLoad(JenkinsRule j) { 83 | // Test that a job config from 1.8.8 can be loaded 84 | // Specifically tests the HttpRequest.xStreamCompatibility() method 85 | FreeStyleProject p = (FreeStyleProject) j.getInstance().getItem("old"); 86 | 87 | List builders = p.getBuilders(); 88 | 89 | HttpRequest httpRequest = (HttpRequest) builders.get(0); 90 | assertEquals("url", httpRequest.getUrl()); 91 | 92 | assertNotNull(httpRequest.getCustomHeaders()); 93 | List customHeaders = httpRequest.getCustomHeaders(); 94 | assertEquals(1,customHeaders.size()); 95 | Iterator itr = customHeaders.iterator(); 96 | HttpRequestNameValuePair nvp = itr.next(); 97 | assertEquals("h1",nvp.getName()); 98 | assertEquals("v1",nvp.getValue()); 99 | 100 | assertNotNull(httpRequest.getValidResponseCodes()); 101 | assertEquals("100:399", httpRequest.getValidResponseCodes()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/HttpRequestGlobalConfig.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import net.sf.json.JSONObject; 8 | 9 | import org.kohsuke.stapler.StaplerRequest2; 10 | 11 | import hudson.Extension; 12 | import hudson.XmlFile; 13 | import hudson.init.InitMilestone; 14 | import hudson.init.Initializer; 15 | import hudson.util.FormValidation; 16 | import hudson.util.XStream2; 17 | import jenkins.model.GlobalConfiguration; 18 | import jenkins.model.Jenkins; 19 | 20 | import jenkins.plugins.http_request.auth.Authenticator; 21 | import jenkins.plugins.http_request.auth.BasicDigestAuthentication; 22 | import jenkins.plugins.http_request.auth.FormAuthentication; 23 | import jenkins.plugins.http_request.util.HttpRequestNameValuePair; 24 | 25 | /** 26 | * @author Martin d'Anjou 27 | */ 28 | @Extension 29 | public class HttpRequestGlobalConfig extends GlobalConfiguration { 30 | 31 | /** 32 | * @deprecated removed without replacement 33 | */ 34 | @Deprecated 35 | private transient List basicDigestAuthentications = new ArrayList<>(); 36 | private List formAuthentications = new ArrayList<>(); 37 | 38 | private static final XStream2 XSTREAM2 = new XStream2(); 39 | 40 | public HttpRequestGlobalConfig() { 41 | load(); 42 | } 43 | 44 | @Initializer(before = InitMilestone.PLUGINS_STARTED) 45 | public static void xStreamCompatibility() { 46 | XSTREAM2.addCompatibilityAlias("jenkins.plugins.http_request.HttpRequest$DescriptorImpl", HttpRequestGlobalConfig.class); 47 | XSTREAM2.addCompatibilityAlias("jenkins.plugins.http_request.util.NameValuePair", HttpRequestNameValuePair.class); 48 | } 49 | 50 | @Override 51 | protected XmlFile getConfigFile() { 52 | File rootDir = Jenkins.get().getRootDir(); 53 | File xmlFile = new File(rootDir, "jenkins.plugins.http_request.HttpRequest.xml"); 54 | return new XmlFile(XSTREAM2, xmlFile); 55 | } 56 | 57 | @Override 58 | public boolean configure(StaplerRequest2 req, JSONObject json) { 59 | req.bindJSON(this, json); 60 | save(); 61 | return true; 62 | } 63 | 64 | public static FormValidation validateKeyName(String value) { 65 | List list = HttpRequestGlobalConfig.get().getAuthentications(); 66 | 67 | int count = 0; 68 | for (Authenticator basicAuthentication : list) { 69 | if (basicAuthentication.getKeyName().equals(value)) { 70 | count++; 71 | } 72 | } 73 | 74 | if (count > 1) { 75 | return FormValidation.error("The Key Name must be unique"); 76 | } 77 | 78 | return FormValidation.validateRequired(value); 79 | } 80 | 81 | public static HttpRequestGlobalConfig get() { 82 | return GlobalConfiguration.all().get(HttpRequestGlobalConfig.class); 83 | } 84 | 85 | /** 86 | * @deprecated removed without replacement 87 | */ 88 | @Deprecated 89 | public List getBasicDigestAuthentications() { 90 | return basicDigestAuthentications; 91 | } 92 | 93 | /** 94 | * @deprecated removed without replacement 95 | */ 96 | @Deprecated 97 | public void setBasicDigestAuthentications( 98 | List basicDigestAuthentications) { 99 | this.basicDigestAuthentications = basicDigestAuthentications; 100 | } 101 | 102 | public List getFormAuthentications() { 103 | return formAuthentications; 104 | } 105 | 106 | public void setFormAuthentications( 107 | List formAuthentications) { 108 | this.formAuthentications = formAuthentications; 109 | } 110 | 111 | public List getAuthentications() { 112 | return new ArrayList<>(formAuthentications); 113 | } 114 | 115 | public Authenticator getAuthentication(String keyName) { 116 | for (Authenticator authenticator : getAuthentications()) { 117 | if (authenticator.getKeyName().equals(keyName)) { 118 | return authenticator; 119 | } 120 | } 121 | return null; 122 | } 123 | 124 | protected Object readResolve() { 125 | if (this.basicDigestAuthentications != null) { 126 | this.basicDigestAuthentications = new ArrayList<>(); 127 | } 128 | return this; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/auth/CredentialBasicAuthentication.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.auth; 2 | 3 | import java.io.PrintStream; 4 | import java.io.Serial; 5 | import java.net.URISyntaxException; 6 | import java.util.Hashtable; 7 | import java.util.Map; 8 | 9 | import org.apache.hc.client5.http.auth.AuthCache; 10 | import org.apache.hc.client5.http.auth.Credentials; 11 | import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; 12 | import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; 13 | import org.apache.hc.client5.http.impl.auth.BasicAuthCache; 14 | import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; 15 | import org.apache.hc.core5.http.HttpHost; 16 | import org.apache.hc.client5.http.auth.AuthScope; 17 | import org.apache.hc.client5.http.protocol.HttpClientContext; 18 | import org.apache.hc.client5.http.utils.URIUtils; 19 | import org.apache.hc.client5.http.impl.auth.BasicScheme; 20 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 21 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 22 | 23 | import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; 24 | 25 | /** 26 | * @author Janario Oliveira 27 | */ 28 | public class CredentialBasicAuthentication implements Authenticator { 29 | @Serial 30 | private static final long serialVersionUID = 8034231374732499786L; 31 | 32 | private final StandardUsernamePasswordCredentials credential; 33 | private final Map extraCredentials = new Hashtable<>(); 34 | 35 | public CredentialBasicAuthentication(StandardUsernamePasswordCredentials credential) { 36 | this.credential = credential; 37 | } 38 | 39 | public void addCredentials(HttpHost host, StandardUsernamePasswordCredentials credentials) { 40 | if (host == null || credentials == null) { 41 | throw new IllegalArgumentException("Null target host or credentials"); 42 | } 43 | extraCredentials.put(host, credential); 44 | } 45 | 46 | @Override 47 | public String getKeyName() { 48 | return credential.getId(); 49 | } 50 | 51 | @Override 52 | public CloseableHttpClient authenticate(HttpClientBuilder clientBuilder, HttpClientContext context, HttpUriRequestBase requestBase, PrintStream logger) { 53 | prepare(clientBuilder, context, requestBase); 54 | return clientBuilder.build(); 55 | } 56 | 57 | public void prepare(HttpClientBuilder clientBuilder, HttpClientContext context, HttpUriRequestBase requestBase) { 58 | try { 59 | prepare(clientBuilder, context, URIUtils.extractHost(requestBase.getUri())); 60 | } catch (URISyntaxException ex) { 61 | throw new IllegalArgumentException(ex); 62 | } 63 | } 64 | 65 | public void prepare(HttpClientBuilder clientBuilder, HttpClientContext context, HttpHost targetHost) { 66 | auth(clientBuilder, context, targetHost, 67 | credential.getUsername(), credential.getPassword().getPlainText(), extraCredentials); 68 | } 69 | 70 | static void auth(HttpClientBuilder clientBuilder, HttpClientContext context, HttpHost targetHost, 71 | String username, String password, Map extraCreds) { 72 | BasicCredentialsProvider provider = new BasicCredentialsProvider(); 73 | AuthCache authCache = new BasicAuthCache(); 74 | 75 | Credentials mainCredentials = new UsernamePasswordCredentials(username, password.toCharArray()); 76 | setCredentials(targetHost, provider, authCache, mainCredentials); 77 | 78 | if (extraCreds != null && !extraCreds.isEmpty()) { 79 | for (Map.Entry creds : extraCreds.entrySet()) { 80 | Credentials extraCredentials = new UsernamePasswordCredentials( 81 | creds.getValue().getUsername(), 82 | creds.getValue().getPassword().getPlainText().toCharArray()); 83 | setCredentials(creds.getKey(), provider, authCache, extraCredentials); 84 | } 85 | } 86 | 87 | clientBuilder.setDefaultCredentialsProvider(provider); 88 | context.setAuthCache(authCache); 89 | context.setCredentialsProvider(provider); 90 | } 91 | 92 | static void setCredentials(HttpHost targetHost, BasicCredentialsProvider provider, AuthCache authCache, Credentials credentials) { 93 | provider.setCredentials(new AuthScope(targetHost), credentials); 94 | 95 | BasicScheme basicScheme = new BasicScheme(); 96 | basicScheme.initPreemptive(credentials); 97 | authCache.put(targetHost, basicScheme); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 4.0.0 27 | 28 | org.jenkins-ci.plugins 29 | plugin 30 | 5.28 31 | 32 | 33 | 34 | http_request 35 | ${revision}${changelist} 36 | hpi 37 | HTTP Request Plugin 38 | 39 | This plugin sends a http request to an url with some parameters. 40 | 41 | https://github.com/jenkinsci/http-request-plugin 42 | 43 | 44 | 45 | MIT License 46 | https://opensource.org/licenses/MIT 47 | 48 | 49 | 50 | 51 | 1.25 52 | -SNAPSHOT 53 | jenkinsci/http-request-plugin 54 | 55 | 2.479 56 | ${jenkins.baseline}.3 57 | Max 58 | true 59 | Low 60 | 1.16 61 | false 62 | 63 | 64 | 65 | 66 | repo.jenkins-ci.org 67 | https://repo.jenkins-ci.org/public/ 68 | 69 | 70 | 71 | 72 | 73 | repo.jenkins-ci.org 74 | https://repo.jenkins-ci.org/public/ 75 | 76 | 77 | 78 | 79 | 80 | 81 | io.jenkins.tools.bom 82 | bom-${jenkins.baseline}.x 83 | 5054.v620b_5d2b_d5e6 84 | import 85 | pom 86 | 87 | 88 | 89 | 90 | 91 | 92 | io.jenkins.plugins 93 | apache-httpcomponents-client-5-api 94 | 95 | 96 | 97 | org.jenkins-ci.plugins 98 | credentials 99 | 100 | 101 | org.jenkins-ci.plugins 102 | script-security 103 | 104 | 105 | org.jenkins-ci.plugins.workflow 106 | workflow-step-api 107 | true 108 | 109 | 110 | 111 | 112 | org.jenkins-ci.plugins.workflow 113 | workflow-step-api 114 | tests 115 | test 116 | 117 | 118 | org.jenkins-ci.plugins.workflow 119 | workflow-basic-steps 120 | 121 | 122 | org.jenkins-ci.plugins.workflow 123 | workflow-cps 124 | test 125 | 126 | 127 | org.jenkins-ci.plugins.workflow 128 | workflow-durable-task-step 129 | 130 | 131 | org.jenkins-ci.plugins.workflow 132 | workflow-job 133 | test 134 | 135 | 136 | 137 | 138 | scm:git:https://github.com/${gitHubRepo}.git 139 | scm:git:git@github.com:${gitHubRepo}.git 140 | https://github.com/${gitHubRepo} 141 | ${scmTag} 142 | 143 | 144 | 145 | janario 146 | Janario Oliveira 147 | janario.oliveira@gmail.com 148 | 149 | 150 | 151 | 152 | Martin d'Anjou 153 | martin.danjou14@gmail.com 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/auth/CertificateAuthentication.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.auth; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintStream; 5 | import java.security.KeyStore; 6 | 7 | import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; 8 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 9 | import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; 10 | import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; 11 | import org.apache.hc.client5.http.protocol.HttpClientContext; 12 | import org.apache.hc.client5.http.socket.ConnectionSocketFactory; 13 | import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; 14 | import org.apache.hc.client5.http.ssl.DefaultHostnameVerifier; 15 | import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; 16 | import org.apache.hc.client5.http.ssl.TrustAllStrategy; 17 | import org.apache.hc.core5.http.config.Registry; 18 | import org.apache.hc.core5.http.config.RegistryBuilder; 19 | import org.apache.hc.core5.ssl.SSLContextBuilder; 20 | import org.apache.hc.core5.ssl.SSLContexts; 21 | import org.apache.hc.core5.ssl.TrustStrategy; 22 | 23 | import com.cloudbees.plugins.credentials.common.StandardCertificateCredentials; 24 | 25 | import hudson.Util; 26 | 27 | public class CertificateAuthentication implements Authenticator { 28 | 29 | private final StandardCertificateCredentials credentials; 30 | 31 | private final boolean ignoreSslErrors; 32 | 33 | public CertificateAuthentication(StandardCertificateCredentials credentials) { 34 | this.credentials = credentials; 35 | this.ignoreSslErrors = false; 36 | } 37 | 38 | public CertificateAuthentication(StandardCertificateCredentials credentials, boolean ignoreSslErrors) { 39 | this.credentials = credentials; 40 | this.ignoreSslErrors = ignoreSslErrors; 41 | } 42 | 43 | @Override 44 | public String getKeyName() { 45 | return credentials.getId(); 46 | } 47 | 48 | @Override 49 | public CloseableHttpClient authenticate(HttpClientBuilder clientBuilder, 50 | HttpClientContext context, 51 | HttpUriRequestBase requestBase, 52 | PrintStream logger) throws IOException { 53 | try { 54 | KeyStore keyStore = credentials.getKeyStore(); 55 | // Note: modeled after CertificateCredentialsImpl.toCharArray() 56 | // which ignores both null and "" empty passwords, even though 57 | // technically the byte stream reader there *can* decipher with 58 | // "" as the password. The null value is explicitly ignored by 59 | // ultimate sun.security.pkcs12.PKCS12KeyStore::engineLoad(), 60 | // for more context see comments in its sources. 61 | String keyStorePass = Util.fixEmpty(credentials.getPassword().getPlainText()); 62 | char[] keyStorePassChars = (keyStorePass == null ? null : keyStorePass.toCharArray()); 63 | SSLContextBuilder contextBuilder = SSLContexts.custom(); 64 | 65 | if (keyStorePassChars == null) { 66 | logger.println("WARNING: Jenkins Certificate Credential '" + 67 | credentials.getId() + "' was saved without a password, " + 68 | "so any certificates (and chain of trust) in it would " + 69 | "be ignored by Java PKCS12 support!"); 70 | } 71 | 72 | try { 73 | TrustStrategy trustStrategy = null; 74 | if (ignoreSslErrors) { 75 | // e.g. for user certificate issued by test CA so 76 | // is not persisted in the system 'cacerts' file. 77 | // Hopefully it is at least added/trusted in the 78 | // generated keystore... 79 | trustStrategy = new TrustAllStrategy(); 80 | //trustStrategy = new TrustSelfSignedStrategy(); 81 | } 82 | 83 | contextBuilder.loadTrustMaterial(keyStore, trustStrategy); 84 | logger.println("Added Trust Material from provided KeyStore"); 85 | } catch (Exception e) { 86 | logger.println("Failed to add Trust Material from provided KeyStore (so Key Material might end up untrusted): " + e.getMessage()); 87 | // Do no re-throw, maybe system trust would suffice? 88 | // TODO: Can we identify lack of trust material in 89 | // key store vs. inability to load what exists?.. 90 | // And do we really care about the difference? 91 | } 92 | 93 | contextBuilder.loadKeyMaterial(keyStore, keyStorePassChars); 94 | logger.println("Added Key Material from provided KeyStore"); 95 | 96 | ConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(contextBuilder.build(), new DefaultHostnameVerifier()); 97 | Registry socketFactoryRegistry = RegistryBuilder. create() 98 | .register("https", sslsf) 99 | .register("http", new PlainConnectionSocketFactory()) 100 | .build(); 101 | PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); 102 | clientBuilder.setConnectionManager(connectionManager); 103 | logger.println("Set SSL context and socket factory for the HTTP client builder"); 104 | 105 | return clientBuilder.build(); 106 | } catch (Exception e) { 107 | logger.println("Failed to set SSL context: " + e.getMessage()); 108 | throw new IOException(e); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/util/HttpClientUtil.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.PrintStream; 6 | import java.net.URISyntaxException; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.List; 9 | 10 | import org.apache.commons.io.IOUtils; 11 | import org.apache.hc.client5.http.classic.HttpClient; 12 | import org.apache.hc.client5.http.classic.methods.HttpDelete; 13 | import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; 14 | import org.apache.hc.client5.http.protocol.HttpClientContext; 15 | import org.apache.hc.client5.http.utils.URIUtils; 16 | import org.apache.hc.core5.http.HttpEntity; 17 | import org.apache.hc.core5.http.HttpHeaders; 18 | import org.apache.hc.core5.http.HttpResponse; 19 | import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; 20 | import org.apache.hc.core5.http.ContentType; 21 | import org.apache.hc.client5.http.classic.methods.HttpGet; 22 | import org.apache.hc.client5.http.classic.methods.HttpHead; 23 | import org.apache.hc.client5.http.classic.methods.HttpOptions; 24 | import org.apache.hc.client5.http.classic.methods.HttpPatch; 25 | import org.apache.hc.client5.http.classic.methods.HttpPost; 26 | import org.apache.hc.client5.http.classic.methods.HttpPut; 27 | import org.apache.hc.core5.http.io.entity.StringEntity; 28 | 29 | import jenkins.plugins.http_request.HttpMode; 30 | 31 | /** 32 | * @author Janario Oliveira 33 | */ 34 | public class HttpClientUtil { 35 | 36 | public HttpUriRequestBase createRequestBase(RequestAction requestAction) throws IOException { 37 | HttpUriRequestBase httpRequestBase = doCreateRequestBase(requestAction); 38 | for (HttpRequestNameValuePair header : requestAction.getHeaders()) { 39 | httpRequestBase.addHeader(header.getName(), header.getValue()); 40 | } 41 | 42 | return httpRequestBase; 43 | } 44 | 45 | private HttpUriRequestBase doCreateRequestBase(RequestAction requestAction) throws IOException { 46 | //without entity 47 | if (requestAction.getMode() == HttpMode.HEAD) { 48 | return new HttpHead(getUrlWithParams(requestAction)); 49 | } else if (requestAction.getMode() == HttpMode.GET && (requestAction.getRequestBody() == null || requestAction.getRequestBody().isEmpty())) { 50 | return new HttpGet(getUrlWithParams(requestAction)); 51 | } 52 | 53 | //with entity 54 | final String uri = requestAction.getUrl().toString(); 55 | HttpUriRequestBase http; 56 | if (requestAction.getMode() == HttpMode.GET) { 57 | http = new HttpGet(getUrlWithParams(requestAction)); 58 | } else if (requestAction.getMode() == HttpMode.DELETE) { 59 | http = new HttpDelete(uri); 60 | } else if (requestAction.getMode() == HttpMode.PUT) { 61 | http = new HttpPut(uri); 62 | } else if (requestAction.getMode() == HttpMode.PATCH) { 63 | http = new HttpPatch(uri); 64 | } else if (requestAction.getMode() == HttpMode.OPTIONS) { 65 | return new HttpOptions(getUrlWithParams(requestAction)); 66 | } else if (requestAction.getMode() == HttpMode.MKCOL) { 67 | return new HttpMkcol(uri); 68 | } else { //default post 69 | http = new HttpPost(uri); 70 | } 71 | 72 | http.setEntity(makeEntity(requestAction)); 73 | return http; 74 | } 75 | 76 | private HttpEntity makeEntity(RequestAction requestAction) { 77 | if (requestAction.getRequestBody() != null && !requestAction.getRequestBody().isEmpty()) { 78 | ContentType contentType = null; 79 | for (HttpRequestNameValuePair header : requestAction.getHeaders()) { 80 | if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(header.getName())) { 81 | contentType = ContentType.parse(header.getValue()); 82 | break; 83 | } 84 | } 85 | 86 | return new StringEntity(requestAction.getRequestBody(), contentType); 87 | } 88 | return toUrlEncoded(requestAction.getParams()); 89 | } 90 | 91 | private String getUrlWithParams(RequestAction requestAction) throws IOException { 92 | String url = requestAction.getUrl().toString(); 93 | 94 | if (!requestAction.getParams().isEmpty()) { 95 | url = appendParamsToUrl(url, requestAction.getParams()); 96 | } 97 | return url; 98 | } 99 | 100 | private static UrlEncodedFormEntity toUrlEncoded(List params) { 101 | return new UrlEncodedFormEntity(params); 102 | } 103 | 104 | public static String appendParamsToUrl(String url, List params) throws IOException { 105 | url += url.contains("?") ? "&" : "?"; 106 | url += paramsToString(params); 107 | 108 | return url; 109 | } 110 | 111 | public static String paramsToString(List params) throws IOException { 112 | try (UrlEncodedFormEntity entity = toUrlEncoded(params); InputStream is = entity.getContent()) { 113 | return IOUtils.toString(is, StandardCharsets.UTF_8); 114 | } 115 | } 116 | 117 | public HttpResponse execute(HttpClient client, HttpClientContext context, HttpUriRequestBase method, 118 | PrintStream logger) throws IOException { 119 | try { 120 | logger.println("Sending request to url: " + method.getUri()); 121 | 122 | final HttpResponse httpResponse = client.executeOpen(URIUtils.extractHost(method.getUri()), method, context); 123 | logger.println("Response Code: " + httpResponse.getCode()); 124 | 125 | return httpResponse; 126 | } catch (URISyntaxException ex) { 127 | throw new IllegalArgumentException(ex); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/ResponseContentSupplier.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.Serial; 8 | import java.io.Serializable; 9 | import java.nio.charset.Charset; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.TreeMap; 14 | 15 | import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; 16 | import org.apache.hc.core5.http.ClassicHttpResponse; 17 | import org.apache.hc.core5.http.ContentType; 18 | import org.apache.hc.core5.http.Header; 19 | import org.apache.hc.core5.http.HttpEntity; 20 | import org.apache.hc.core5.http.HttpHeaders; 21 | import org.apache.hc.core5.http.HttpResponse; 22 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 23 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted; 24 | 25 | import org.apache.commons.io.IOUtils; 26 | 27 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 28 | 29 | /** 30 | * A container for the Http Response. 31 | *

32 | * The container is returned as is to the Pipeline. 33 | * For the normal plugin, the container is consumed internally (since it cannot be returned). 34 | * 35 | * @author Martin d'Anjou 36 | */ 37 | public class ResponseContentSupplier implements Serializable, AutoCloseable { 38 | 39 | @Serial 40 | private static final long serialVersionUID = 1L; 41 | 42 | private final int status; 43 | private final Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 44 | private String charset; 45 | 46 | private ResponseHandle responseHandle; 47 | private String content; 48 | @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") 49 | private transient InputStream contentStream; 50 | @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") 51 | private transient CloseableHttpClient httpclient; 52 | 53 | public ResponseContentSupplier(String content, int status) { 54 | this.content = content; 55 | this.status = status; 56 | } 57 | 58 | public ResponseContentSupplier(ResponseHandle responseHandle, CloseableHttpResponse response) { 59 | this.status = response.getCode(); 60 | this.responseHandle = responseHandle; 61 | readHeaders(response); 62 | readCharset(response); 63 | 64 | try { 65 | HttpEntity entity = response.getEntity(); 66 | InputStream entityContent = entity != null ? entity.getContent() : null; 67 | 68 | if (responseHandle == ResponseHandle.STRING && entityContent != null) { 69 | byte[] bytes = IOUtils.toByteArray(entityContent); 70 | contentStream = new ByteArrayInputStream(bytes); 71 | content = new String(bytes, charset == null || charset.isEmpty() ? 72 | Charset.defaultCharset().name() : charset); 73 | } else { 74 | contentStream = entityContent; 75 | } 76 | } catch (IOException e) { 77 | throw new IllegalStateException(e); 78 | } 79 | } 80 | 81 | @Whitelisted 82 | public int getStatus() { 83 | return this.status; 84 | } 85 | 86 | @Whitelisted 87 | public Map> getHeaders() { 88 | return this.headers; 89 | } 90 | 91 | @Whitelisted 92 | public String getCharset() { 93 | return charset; 94 | } 95 | 96 | @Whitelisted 97 | public String getContent() { 98 | if (responseHandle == ResponseHandle.STRING) { 99 | return content; 100 | } 101 | if (content != null) { 102 | return content; 103 | } 104 | if (contentStream == null) { 105 | return null; 106 | } 107 | 108 | try (InputStreamReader in = new InputStreamReader(contentStream, 109 | charset == null || charset.isEmpty() ? Charset.defaultCharset().name() : charset)) { 110 | content = IOUtils.toString(in); 111 | return content; 112 | } catch (IOException e) { 113 | throw new IllegalStateException("Error reading response. " + 114 | "If you are reading the content in pipeline you should pass responseHandle: 'LEAVE_OPEN' and " + 115 | "close the response(response.close()) after consume it.", e); 116 | } 117 | } 118 | 119 | @Whitelisted 120 | public InputStream getContentStream() { 121 | return contentStream; 122 | } 123 | 124 | private void readCharset(ClassicHttpResponse response) { 125 | Charset charset = null; 126 | 127 | Header contentTypeHeader = response.getFirstHeader(HttpHeaders.CONTENT_TYPE); 128 | ContentType contentType = ContentType.parse(response.getEntity() != null ? 129 | response.getEntity().getContentType() : 130 | (contentTypeHeader != null ? contentTypeHeader.getValue() : null)); 131 | if (contentType != null) { 132 | charset = contentType.getCharset(); 133 | if (charset == null) { 134 | ContentType defaultContentType = ContentType.create(contentType.getMimeType()); 135 | charset = defaultContentType.getCharset(); 136 | } 137 | } 138 | if (charset != null) { 139 | this.charset = charset.name(); 140 | } 141 | } 142 | 143 | private void readHeaders(HttpResponse response) { 144 | Header[] respHeaders = response.getHeaders(); 145 | for (Header respHeader : respHeaders) { 146 | List hs = headers.computeIfAbsent(respHeader.getName(), k -> new ArrayList<>()); 147 | hs.add(respHeader.getValue()); 148 | } 149 | } 150 | 151 | @Override 152 | public String toString() { 153 | return "Status: " + this.status; 154 | } 155 | 156 | @Override 157 | public void close() throws IOException { 158 | if (httpclient != null) { 159 | httpclient.close(); 160 | } 161 | if (contentStream != null) { 162 | contentStream.close(); 163 | } 164 | } 165 | 166 | void setHttpClient(CloseableHttpClient httpclient) { 167 | this.httpclient = httpclient; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/http_request/HttpRequestStepRoundTripTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.net.URL; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import jenkins.plugins.http_request.auth.FormAuthentication; 10 | import jenkins.plugins.http_request.util.HttpRequestNameValuePair; 11 | import jenkins.plugins.http_request.util.RequestAction; 12 | 13 | import org.jenkinsci.plugins.workflow.steps.StepConfigTester; 14 | import org.junit.jupiter.api.Test; 15 | 16 | import org.jvnet.hudson.test.JenkinsRule; 17 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 18 | 19 | /** 20 | * @author Martin d'Anjou 21 | */ 22 | @WithJenkins 23 | class HttpRequestStepRoundTripTest { 24 | 25 | private static final HttpRequestStep before = new HttpRequestStep("http://domain/"); 26 | 27 | @Test 28 | void configRoundTripGroup1(JenkinsRule j) throws Exception { 29 | configRoundTrip(j); 30 | before.setHttpMode(HttpMode.GET); 31 | configRoundTrip(j); 32 | before.setValidResponseCodes("100:599"); 33 | configRoundTrip(j); 34 | before.setValidResponseContent("some content we want to see"); 35 | configRoundTrip(j); 36 | before.setAcceptType(MimeType.TEXT_HTML); 37 | configRoundTrip(j); 38 | before.setContentType(MimeType.TEXT_HTML); 39 | configRoundTrip(j); 40 | } 41 | 42 | @Test 43 | void configRoundtripGroup2(JenkinsRule j) throws Exception { 44 | before.setTimeout(12); 45 | configRoundTrip(j); 46 | before.setConsoleLogResponseBody(true); 47 | configRoundTrip(j); 48 | before.setConsoleLogResponseBody(false); 49 | configRoundTrip(j); 50 | } 51 | 52 | @Test 53 | void configRoundtripGroup3(JenkinsRule j) throws Exception { 54 | configRoundTrip(j); 55 | 56 | List params = new ArrayList<>(); 57 | params.add(new HttpRequestNameValuePair("param1","value1")); 58 | params.add(new HttpRequestNameValuePair("param2","value2")); 59 | 60 | RequestAction action = new RequestAction(new URL("http://www.domain.com/"),HttpMode.GET,null,params); 61 | List actions = new ArrayList<>(); 62 | actions.add(action); 63 | 64 | FormAuthentication formAuth = new FormAuthentication("keyname",actions); 65 | List formAuthList = new ArrayList<>(); 66 | formAuthList.add(formAuth); 67 | 68 | HttpRequestGlobalConfig.get().setFormAuthentications(formAuthList); 69 | configRoundTrip(j); 70 | 71 | List customHeaders = new ArrayList<>(); 72 | customHeaders.add(new HttpRequestNameValuePair("param1","value1")); 73 | before.setCustomHeaders(customHeaders); 74 | configRoundTrip(j); 75 | } 76 | 77 | @Test 78 | void configRoundtripGroup4(JenkinsRule j) throws Exception { 79 | before.setUploadFile("upload.txt"); 80 | configRoundTrip(j); 81 | before.setMultipartName("filename"); 82 | configRoundTrip(j); 83 | } 84 | 85 | private static void configRoundTrip(JenkinsRule j) throws Exception { 86 | HttpRequestStep after = new StepConfigTester(j).configRoundTrip(before); 87 | j.assertEqualBeans(before, after, "httpMode"); 88 | j.assertEqualBeans(before, after, "url"); 89 | j.assertEqualBeans(before, after, "validResponseCodes"); 90 | j.assertEqualBeans(before, after, "validResponseContent"); 91 | j.assertEqualBeans(before, after, "acceptType"); 92 | j.assertEqualBeans(before, after, "contentType"); 93 | j.assertEqualBeans(before, after, "uploadFile"); 94 | j.assertEqualBeans(before, after, "multipartName"); 95 | j.assertEqualBeans(before, after, "timeout"); 96 | j.assertEqualBeans(before, after, "consoleLogResponseBody"); 97 | j.assertEqualBeans(before, after, "authentication"); 98 | 99 | // Custom header check 100 | assertEquals(before.getCustomHeaders().size(),after.getCustomHeaders().size()); 101 | for (int idx = 0; idx < before.getCustomHeaders().size(); idx++) { 102 | HttpRequestNameValuePair bnvp = before.getCustomHeaders().get(idx); 103 | HttpRequestNameValuePair anvp = after.getCustomHeaders().get(idx); 104 | assertEquals(bnvp.getName(),anvp.getName()); 105 | assertEquals(bnvp.getValue(),anvp.getValue()); 106 | } 107 | 108 | // Form authentication check 109 | List beforeFas = HttpRequestGlobalConfig.get().getFormAuthentications(); 110 | List afterFas = HttpRequestGlobalConfig.get().getFormAuthentications(); 111 | assertEquals(beforeFas.size(), afterFas.size()); 112 | for (int idx = 0; idx < beforeFas.size(); idx++) { 113 | FormAuthentication beforeFa = beforeFas.get(idx); 114 | FormAuthentication afterFa = afterFas.get(idx); 115 | assertEquals(beforeFa.getKeyName(), afterFa.getKeyName()); 116 | List beforeActions = beforeFa.getActions(); 117 | List afterActions = afterFa.getActions(); 118 | assertEquals(beforeActions.size(), afterActions.size()); 119 | for (int jdx = 0; jdx < beforeActions.size(); jdx ++) { 120 | RequestAction beforeAction = beforeActions.get(jdx); 121 | RequestAction afterAction = afterActions.get(jdx); 122 | assertEquals(beforeAction.getUrl(), afterAction.getUrl()); 123 | assertEquals(beforeAction.getMode(), afterAction.getMode()); 124 | List beforeParams = beforeAction.getParams(); 125 | List afterParams = afterAction.getParams(); 126 | assertEquals(beforeParams.size(), afterParams.size()); 127 | for (int kdx = 0; kdx < beforeParams.size(); kdx++) { 128 | HttpRequestNameValuePair beforeNvp = beforeParams.get(kdx); 129 | HttpRequestNameValuePair afterNvp = afterParams.get(kdx); 130 | assertEquals(beforeNvp.getName(), afterNvp.getName()); 131 | assertEquals(beforeNvp.getValue(), afterNvp.getValue()); 132 | } 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/http_request/HttpRequestRoundTripTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.jvnet.hudson.test.JenkinsRule; 6 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 7 | 8 | import java.net.URL; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import jenkins.plugins.http_request.auth.FormAuthentication; 15 | import jenkins.plugins.http_request.util.HttpRequestNameValuePair; 16 | import jenkins.plugins.http_request.util.RequestAction; 17 | 18 | /** 19 | * @author Martin d'Anjou 20 | */ 21 | @WithJenkins 22 | class HttpRequestRoundTripTest { 23 | 24 | private static final HttpRequest before = new HttpRequest("http://domain/"); 25 | 26 | @Test 27 | void configRoundtripGroup1(JenkinsRule j) throws Exception { 28 | configRoundTrip(j); 29 | before.setHttpMode(HttpMode.GET); 30 | configRoundTrip(j); 31 | before.setPassBuildParameters(true); 32 | configRoundTrip(j); 33 | before.setPassBuildParameters(false); 34 | configRoundTrip(j); 35 | } 36 | 37 | @Test 38 | void configRoundTripGroup1b(JenkinsRule j) throws Exception { 39 | before.setValidResponseCodes("100:599"); 40 | configRoundTrip(j); 41 | before.setValidResponseContent("some content we want to see"); 42 | configRoundTrip(j); 43 | before.setAcceptType(MimeType.TEXT_HTML); 44 | configRoundTrip(j); 45 | before.setContentType(MimeType.TEXT_HTML); 46 | configRoundTrip(j); 47 | } 48 | 49 | @Test 50 | void configRoundtripGroup2(JenkinsRule j) throws Exception { 51 | before.setOutputFile("myfile.txt"); 52 | configRoundTrip(j); 53 | before.setTimeout(12); 54 | configRoundTrip(j); 55 | before.setConsoleLogResponseBody(true); 56 | configRoundTrip(j); 57 | before.setConsoleLogResponseBody(false); 58 | configRoundTrip(j); 59 | } 60 | 61 | @Test 62 | void configRoundtripGroup3(JenkinsRule j) throws Exception { 63 | configRoundTrip(j); 64 | 65 | List params = new ArrayList<>(); 66 | params.add(new HttpRequestNameValuePair("param1","value1")); 67 | params.add(new HttpRequestNameValuePair("param2","value2")); 68 | 69 | RequestAction action = new RequestAction(new URL("http://www.domain.com/"),HttpMode.GET,null,params); 70 | List actions = new ArrayList<>(); 71 | actions.add(action); 72 | 73 | FormAuthentication formAuth = new FormAuthentication("keyname",actions); 74 | List formAuthList = new ArrayList<>(); 75 | formAuthList.add(formAuth); 76 | 77 | HttpRequestGlobalConfig.get().setFormAuthentications(formAuthList); 78 | configRoundTrip(j); 79 | 80 | List customHeaders = new ArrayList<>(); 81 | customHeaders.add(new HttpRequestNameValuePair("param1","value1")); 82 | before.setCustomHeaders(customHeaders); 83 | configRoundTrip(j); 84 | } 85 | 86 | @Test 87 | void configRoundtripGroup4(JenkinsRule j) throws Exception { 88 | before.setUploadFile("upload.txt"); 89 | configRoundTrip(j); 90 | before.setMultipartName("filename"); 91 | configRoundTrip(j); 92 | } 93 | 94 | private static void configRoundTrip(JenkinsRule j) throws Exception { 95 | HttpRequest after = j.configRoundtrip(before); 96 | j.assertEqualBeans(before, after, "httpMode,passBuildParameters"); 97 | j.assertEqualBeans(before, after, "url"); 98 | j.assertEqualBeans(HttpRequestRoundTripTest.before, after, "validResponseCodes,validResponseContent"); 99 | j.assertEqualBeans(before, after, "acceptType,contentType"); 100 | j.assertEqualBeans(before, after, "uploadFile,multipartName"); 101 | j.assertEqualBeans(before, after, "outputFile,timeout"); 102 | j.assertEqualBeans(before, after, "consoleLogResponseBody"); 103 | j.assertEqualBeans(before, after, "authentication"); 104 | 105 | // Custom header check 106 | assertEquals(before.getCustomHeaders().size(),after.getCustomHeaders().size()); 107 | for (int idx = 0; idx < before.getCustomHeaders().size(); idx++) { 108 | HttpRequestNameValuePair bnvp = before.getCustomHeaders().get(idx); 109 | HttpRequestNameValuePair anvp = after.getCustomHeaders().get(idx); 110 | assertEquals(bnvp.getName(),anvp.getName()); 111 | assertEquals(bnvp.getValue(),anvp.getValue()); 112 | } 113 | 114 | // Form authentication check 115 | List beforeFas = HttpRequestGlobalConfig.get().getFormAuthentications(); 116 | List afterFas = HttpRequestGlobalConfig.get().getFormAuthentications(); 117 | assertEquals(beforeFas.size(), afterFas.size()); 118 | for (int idx = 0; idx < beforeFas.size(); idx++) { 119 | FormAuthentication beforeFa = beforeFas.get(idx); 120 | FormAuthentication afterFa = afterFas.get(idx); 121 | assertEquals(beforeFa.getKeyName(), afterFa.getKeyName()); 122 | List beforeActions = beforeFa.getActions(); 123 | List afterActions = afterFa.getActions(); 124 | assertEquals(beforeActions.size(), afterActions.size()); 125 | for (int jdx = 0; jdx < beforeActions.size(); jdx ++) { 126 | RequestAction beforeAction = beforeActions.get(jdx); 127 | RequestAction afterAction = afterActions.get(jdx); 128 | assertEquals(beforeAction.getUrl(), afterAction.getUrl()); 129 | assertEquals(beforeAction.getMode(), afterAction.getMode()); 130 | List beforeParams = beforeAction.getParams(); 131 | List afterParams = afterAction.getParams(); 132 | assertEquals(beforeParams.size(), afterParams.size()); 133 | for (int kdx = 0; kdx < beforeParams.size(); kdx++) { 134 | HttpRequestNameValuePair beforeNvp = beforeParams.get(kdx); 135 | HttpRequestNameValuePair afterNvp = afterParams.get(kdx); 136 | assertEquals(beforeNvp.getName(), afterNvp.getName()); 137 | assertEquals(beforeNvp.getValue(), afterNvp.getValue()); 138 | } 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 144 | 146 | -------------------------------------------------------------------------------- /src/test/resources/jenkins/plugins/http_request/README.txt: -------------------------------------------------------------------------------- 1 | Test keystores: 2 | 3 | * test.p12 was picked from credentials-plugin 4 | It includes one entry aliased "1" with a private key, 5 | its cert, and issuing CA cert (as a simple chain). 6 | The keystore password is "password" 7 | 8 | * testTrusted.p12 is a modification of that, adding an 9 | entry aliased "ca" with that same CA cert imported as 10 | trusted: 11 | ```` 12 | # Show certs in BASE64 format, last of these is CA: 13 | :; keytool -list -keystore test.p12 -storepass password -alias 1 -rfc 14 | 15 | # Save second block to "ca.pem" 16 | 17 | # Re-import: 18 | :; cp test.p12 testTrusted.p12 19 | :; keytool -importcert -trustcacerts -alias ca -file ca.pem -keystore testTrusted.p12 -storepass password 20 | ... 21 | Trust this certificate? [no]: yes 22 | Certificate was added to keystore 23 | ```` 24 | 25 | Verification: 26 | ```` 27 | :; keytool -list -keystore testTrusted.p12 -storepass password -rfc 28 | Keystore type: PKCS12 29 | Keystore provider: SUN 30 | 31 | Your keystore contains 2 entries 32 | 33 | Alias name: 1 34 | Creation date: 23.11.2022 35 | Entry type: PrivateKeyEntry 36 | Certificate chain length: 2 37 | Certificate[1]: 38 | -----BEGIN CERTIFICATE----- 39 | MIIDRzCCArCgAwIBAgIBATANBgkqhkiG9w0BAQQFADBmMQswCQYDVQQGEwJLRzEL 40 | MAkGA1UECBMCTkExEDAOBgNVBAcTB0JJU0hLRUsxFTATBgNVBAoTDE9wZW5WUE4t 41 | VEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTA1MDgw 42 | NDE4MTYyMFoXDTE1MDgwMjE4MTYyMFowfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgT 43 | AkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZvcnQtRnVuc3Rv 44 | bjEPMA0GA1UEAxMGcGtjczEyMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlk 45 | b21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMOT0PRbWiTEJTUjjiwW 46 | yPC7hR2ruxshzWcgWZUuNg5RARnnsQfGpBK+kKp4QsJSunVCo2fmUFkU/UGYVVXK 47 | nHMEcDtX2JqVY/bAPjxptn5k1bnvMFkKFnaAZl5Mi0K0s+D9U0ivpIaw1QXdQbw+ 48 | w3STcv1kpy8rmyerH6KOXL1bAgMBAAGjge4wgeswCQYDVR0TBAIwADAsBglghkgB 49 | hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE 50 | FP9dcedV6TFtLIWOWXxIQ5h6JR45MIGQBgNVHSMEgYgwgYWAFImmYOO66j6v/GR/ 51 | TL2M0kiN4MxGoWqkaDBmMQswCQYDVQQGEwJLRzELMAkGA1UECBMCTkExEDAOBgNV 52 | BAcTB0JJU0hLRUsxFTATBgNVBAoTDE9wZW5WUE4tVEVTVDEhMB8GCSqGSIb3DQEJ 53 | ARYSbWVAbXlob3N0Lm15ZG9tYWluggEAMA0GCSqGSIb3DQEBBAUAA4GBABP/5mXw 54 | ttXKG6dqQl5kPisFs/c+0j64xytp5/cdB/zMpEWRWTBXtyL3T5T16xs52kJS0VfT 55 | t+jezYbeu/dCdBL8Moz3RTYb1aY2/xymZ433kWjvgtrOzgGlaW3eKXcQpQEyK2v/ 56 | J4q7+oDCElBRilZCm0mBcQsySKsZjGm8BMjh 57 | -----END CERTIFICATE----- 58 | Certificate[2]: 59 | -----BEGIN CERTIFICATE----- 60 | MIIDBjCCAm+gAwIBAgIBADANBgkqhkiG9w0BAQQFADBmMQswCQYDVQQGEwJLRzEL 61 | MAkGA1UECBMCTkExEDAOBgNVBAcTB0JJU0hLRUsxFTATBgNVBAoTDE9wZW5WUE4t 62 | VEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTA0MTEy 63 | NTE0NDA1NVoXDTE0MTEyMzE0NDA1NVowZjELMAkGA1UEBhMCS0cxCzAJBgNVBAgT 64 | Ak5BMRAwDgYDVQQHEwdCSVNIS0VLMRUwEwYDVQQKEwxPcGVuVlBOLVRFU1QxITAf 65 | BgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRvbWFpbjCBnzANBgkqhkiG9w0BAQEF 66 | AAOBjQAwgYkCgYEAqPjWJnesPu6bR/iec4FMz3opVaPdBHxg+ORKNmrnVZPh0t8/ 67 | ZT34KXkYoI9B82scurp8UlZVXG8JdUsz+yai8ti9+g7vcuyKUtcCIjn0HLgmdPu5 68 | gFX25lB0pXw+XIU031dOfPvtROdG5YZN5yCErgCy7TE7zntLnkEDuRmyU6cCAwEA 69 | AaOBwzCBwDAdBgNVHQ4EFgQUiaZg47rqPq/8ZH9MvYzSSI3gzEYwgZAGA1UdIwSB 70 | iDCBhYAUiaZg47rqPq/8ZH9MvYzSSI3gzEahaqRoMGYxCzAJBgNVBAYTAktHMQsw 71 | CQYDVQQIEwJOQTEQMA4GA1UEBxMHQklTSEtFSzEVMBMGA1UEChMMT3BlblZQTi1U 72 | RVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW6CAQAwDAYDVR0T 73 | BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBfJoiWYrYdjM0mKPEzUQk0nLYTovBP 74 | I0es/2rfGrin1zbcFY+4dhVBd1E/StebnG+CP8r7QeEIwu7x8gYDdOLLsZn+2vBL 75 | e4jNU1ClI6Q0L7jrzhhunQ5mAaZztVyYwFB15odYcdN2iO0tP7jtEsvrRqxICNy3 76 | 8itzViPTf5W4sA== 77 | -----END CERTIFICATE----- 78 | 79 | 80 | ******************************************* 81 | ******************************************* 82 | 83 | 84 | Alias name: ca 85 | Creation date: 23.11.2022 86 | Entry type: trustedCertEntry 87 | 88 | -----BEGIN CERTIFICATE----- 89 | MIIDBjCCAm+gAwIBAgIBADANBgkqhkiG9w0BAQQFADBmMQswCQYDVQQGEwJLRzEL 90 | MAkGA1UECBMCTkExEDAOBgNVBAcTB0JJU0hLRUsxFTATBgNVBAoTDE9wZW5WUE4t 91 | VEVTVDEhMB8GCSqGSIb3DQEJARYSbWVAbXlob3N0Lm15ZG9tYWluMB4XDTA0MTEy 92 | NTE0NDA1NVoXDTE0MTEyMzE0NDA1NVowZjELMAkGA1UEBhMCS0cxCzAJBgNVBAgT 93 | Ak5BMRAwDgYDVQQHEwdCSVNIS0VLMRUwEwYDVQQKEwxPcGVuVlBOLVRFU1QxITAf 94 | BgkqhkiG9w0BCQEWEm1lQG15aG9zdC5teWRvbWFpbjCBnzANBgkqhkiG9w0BAQEF 95 | AAOBjQAwgYkCgYEAqPjWJnesPu6bR/iec4FMz3opVaPdBHxg+ORKNmrnVZPh0t8/ 96 | ZT34KXkYoI9B82scurp8UlZVXG8JdUsz+yai8ti9+g7vcuyKUtcCIjn0HLgmdPu5 97 | gFX25lB0pXw+XIU031dOfPvtROdG5YZN5yCErgCy7TE7zntLnkEDuRmyU6cCAwEA 98 | AaOBwzCBwDAdBgNVHQ4EFgQUiaZg47rqPq/8ZH9MvYzSSI3gzEYwgZAGA1UdIwSB 99 | iDCBhYAUiaZg47rqPq/8ZH9MvYzSSI3gzEahaqRoMGYxCzAJBgNVBAYTAktHMQsw 100 | CQYDVQQIEwJOQTEQMA4GA1UEBxMHQklTSEtFSzEVMBMGA1UEChMMT3BlblZQTi1U 101 | RVNUMSEwHwYJKoZIhvcNAQkBFhJtZUBteWhvc3QubXlkb21haW6CAQAwDAYDVR0T 102 | BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQBfJoiWYrYdjM0mKPEzUQk0nLYTovBP 103 | I0es/2rfGrin1zbcFY+4dhVBd1E/StebnG+CP8r7QeEIwu7x8gYDdOLLsZn+2vBL 104 | e4jNU1ClI6Q0L7jrzhhunQ5mAaZztVyYwFB15odYcdN2iO0tP7jtEsvrRqxICNy3 105 | 8itzViPTf5W4sA== 106 | -----END CERTIFICATE----- 107 | 108 | 109 | ******************************************* 110 | ******************************************* 111 | 112 | 113 | 114 | Warning: 115 | <1> uses the MD5withRSA signature algorithm which is considered a security risk and is disabled. 116 | <1> uses a 1024-bit RSA key which is considered a security risk. This key size will be disabled in a future update. 117 | <1> uses a 1024-bit RSA key which is considered a security risk. This key size will be disabled in a future update. 118 | uses a 1024-bit RSA key which is considered a security risk. This key size will be disabled in a future update. 119 | 120 | 121 | 122 | # Keytool forbids to export private key; openssl can do it: 123 | :; openssl pkcs12 -in testTrusted.p12 -nodes -nocerts -password pass:password 124 | Bag Attributes 125 | friendlyName: 1 126 | localKeyID: 2E 39 A5 71 AE FD F1 64 40 83 69 72 3A B6 3D 64 05 18 58 B7 127 | Key Attributes: 128 | -----BEGIN PRIVATE KEY----- 129 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMOT0PRbWiTEJTUj 130 | jiwWyPC7hR2ruxshzWcgWZUuNg5RARnnsQfGpBK+kKp4QsJSunVCo2fmUFkU/UGY 131 | VVXKnHMEcDtX2JqVY/bAPjxptn5k1bnvMFkKFnaAZl5Mi0K0s+D9U0ivpIaw1QXd 132 | Qbw+w3STcv1kpy8rmyerH6KOXL1bAgMBAAECgYBommqsByATggUUgsvLsPQQLXto 133 | /yy3ukCN47OGIo0u4wxfupfovMmMbPga9O9f17d6eAXF0F0xCBTcPImHtTIvMLIt 134 | UY4U4xwtdlEM3G5ToBxNvCHvtkkDiUVW8AorZfLFY9Agnsc3cTarrvEkdtzyYN8k 135 | 246tqACTJZEW8b/QoQJBAP/cd5sEPACChyHx7jr44mMBUppfu/5QC3+0N39XhTx+ 136 | gXNfpRe/V77Qn0CMcH8RqkQVWTaSzPrpzJcAXgc9cB8CQQDDrvois8c+5ZSGu8MG 137 | 1zNCEjxTU9BBjWEkGLgwMwsH+5BlA7QT8B9QGWYiCJ4pJVJQ37AyQrUqt4at19Yb 138 | vdtFAkEAgylFtxXInIpNM72N3nVPuGkpKzIAcTIfcuuzt3fqOUSwn7BcNXxFQvA3 139 | cyOLV9h6bER1Y2CF6+qGkrIBgbyhCQJBAKR14vRXdBWAjhvOolKVexcEjH7b6iOt 140 | 1v6nZ+XagGLtIqZDPo2jOi3vqs7fv02FeHFQDp2vQuPr6t0gkWovXqECQFEAAAj3 141 | TKXvRs1jL5gKNifOBJgeEqzZJXpLMkWDGTgImu+VyKcGE6+pie0okh4rmIoJqNx0 142 | EzBIslSYYUz8Q+A= 143 | -----END PRIVATE KEY----- 144 | ```` 145 | 146 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/http_request/HttpRequestTestBase.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.both; 5 | import static org.hamcrest.Matchers.greaterThanOrEqualTo; 6 | import static org.hamcrest.Matchers.is; 7 | import static org.hamcrest.Matchers.lessThan; 8 | 9 | import java.io.IOException; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | import jakarta.servlet.ServletException; 17 | 18 | import org.apache.hc.core5.http.ContentType; 19 | import org.eclipse.jetty.http.HttpHeader; 20 | import org.eclipse.jetty.http.HttpStatus; 21 | import org.eclipse.jetty.io.Content; 22 | import org.eclipse.jetty.server.Connector; 23 | import org.eclipse.jetty.server.Handler; 24 | import org.eclipse.jetty.server.Request; 25 | import org.eclipse.jetty.server.Response; 26 | import org.eclipse.jetty.server.Server; 27 | import org.eclipse.jetty.server.ServerConnector; 28 | import org.eclipse.jetty.server.handler.ContextHandler; 29 | import org.eclipse.jetty.server.handler.DefaultHandler; 30 | import org.eclipse.jetty.util.Callback; 31 | import org.junit.jupiter.api.AfterAll; 32 | import org.junit.jupiter.api.AfterEach; 33 | import org.junit.jupiter.api.BeforeAll; 34 | import org.junit.jupiter.api.BeforeEach; 35 | import org.jvnet.hudson.test.JenkinsRule; 36 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 37 | 38 | import hudson.model.Descriptor.FormException; 39 | import com.cloudbees.plugins.credentials.Credentials; 40 | import com.cloudbees.plugins.credentials.CredentialsScope; 41 | import com.cloudbees.plugins.credentials.SystemCredentialsProvider; 42 | import com.cloudbees.plugins.credentials.domains.Domain; 43 | import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; 44 | 45 | /** 46 | * @author Martin d'Anjou 47 | */ 48 | @WithJenkins 49 | class HttpRequestTestBase { 50 | 51 | private static ServerRunning SERVER; 52 | static final String ALL_IS_WELL = "All is well"; 53 | protected JenkinsRule j; 54 | 55 | private Map> credentials; 56 | 57 | final String baseURL() { 58 | return SERVER.baseURL; 59 | } 60 | 61 | void registerBasicCredential(String id, String username, String password) throws FormException { 62 | credentials.get(Domain.global()).add( 63 | new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, 64 | id, "", username, password)); 65 | SystemCredentialsProvider.getInstance().setDomainCredentialsMap(credentials); 66 | } 67 | 68 | static void registerHandler(String target, HttpMode method, SimpleHandler handler) { 69 | Map handlerByMethod = SERVER.handlersByMethodByTarget.computeIfAbsent(target, k -> new HashMap<>()); 70 | handlerByMethod.put(method, handler); 71 | } 72 | 73 | @BeforeAll 74 | static void beforeClass() throws Exception { 75 | if (SERVER != null) { 76 | return; 77 | } 78 | SERVER = new ServerRunning(); 79 | } 80 | 81 | @AfterAll 82 | static void afterClass() throws Exception { 83 | if (SERVER != null) { 84 | SERVER.server.stop(); 85 | SERVER = null; 86 | } 87 | } 88 | 89 | @BeforeEach 90 | void init(JenkinsRule j) { 91 | this.j = j; 92 | credentials = new HashMap<>(); 93 | credentials.put(Domain.global(), new ArrayList<>()); 94 | } 95 | 96 | @AfterEach 97 | void cleanHandlers() { 98 | if (SERVER != null) { 99 | SERVER.handlersByMethodByTarget.clear(); 100 | } 101 | } 102 | 103 | protected abstract static class SimpleHandler extends Handler.Abstract { 104 | @Override 105 | public final boolean handle(Request request, Response response, Callback callback) throws IOException, ServletException { 106 | return doHandle(request, response, callback); 107 | } 108 | 109 | String requestBody(Request request) throws IOException { 110 | return Content.Source.asString(request, StandardCharsets.UTF_8); 111 | } 112 | 113 | boolean okAllIsWell(Response response, Callback callback) { 114 | return okText(response, ALL_IS_WELL, callback); 115 | } 116 | 117 | boolean okText(Response response, String body, Callback callback) { 118 | return body(response, HttpStatus.OK_200, ContentType.TEXT_PLAIN, body, callback); 119 | } 120 | 121 | boolean body(Response response, int status, ContentType contentType, String body, Callback callback) { 122 | assertThat(status, is(both(greaterThanOrEqualTo(200)).and(lessThan(300)))); 123 | if (contentType != null) { 124 | response.getHeaders().add(HttpHeader.CONTENT_TYPE, contentType.toString()); 125 | } 126 | response.setStatus(status); 127 | Content.Sink.write(response, true, body, callback); 128 | return true; 129 | } 130 | 131 | abstract boolean doHandle(Request request, Response response, Callback callback) throws IOException, ServletException; 132 | } 133 | 134 | private static final class ServerRunning { 135 | private final Server server; 136 | private final int port; 137 | private final String baseURL; 138 | private final Map> handlersByMethodByTarget = new HashMap<>(); 139 | 140 | private ServerRunning() throws Exception { 141 | server = new Server(); 142 | ServerConnector connector = new ServerConnector(server); 143 | server.setConnectors(new Connector[]{connector}); 144 | 145 | ContextHandler context = new ContextHandler(); 146 | context.setContextPath("/"); 147 | context.setHandler(new DefaultHandler() { 148 | @Override 149 | public boolean handle(Request request, Response response, Callback callback) throws Exception { 150 | String target = request.getHttpURI().getPath(); 151 | Map handlerByMethod = handlersByMethodByTarget.get(target); 152 | if (handlerByMethod != null) { 153 | Handler handler = handlerByMethod.get(HttpMode.valueOf(request.getMethod())); 154 | if (handler != null) { 155 | return handler.handle(request, response, callback); 156 | } 157 | } 158 | 159 | return super.handle(request, response, callback); 160 | } 161 | }); 162 | server.setHandler(context); 163 | 164 | server.start(); 165 | port = connector.getLocalPort(); 166 | baseURL = "http://localhost:" + port; 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | [[http-request-plugin]] 2 | = Http Request Plugin 3 | :toc: macro 4 | :toc-title: HTTP Request Plugin ToC 5 | ifdef::env-github[] 6 | :tip-caption: :bulb: 7 | :note-caption: :information_source: 8 | :important-caption: :heavy_exclamation_mark: 9 | :caution-caption: :fire: 10 | :warning-caption: :warning: 11 | endif::[] 12 | 13 | link:https://ci.jenkins.io/job/Plugins/job/http-request-plugin/job/master/[image:https://ci.jenkins.io/job/Plugins/job/http-request-plugin/job/master/badge/icon[Build]] 14 | link:https://github.com/jenkinsci/http-request-plugin/graphs/contributors[image:https://img.shields.io/github/contributors/jenkinsci/http-request-plugin.svg?color=blue[Contributors]] 15 | link:https://plugins.jenkins.io/http_request/[image:https://img.shields.io/jenkins/plugin/i/http_request.svg?color=blue&label=installations[Jenkins Plugin Installs]] 16 | link:https://plugins.jenkins.io/http_request/[image:https://img.shields.io/jenkins/plugin/v/http_request.svg[Plugin]] 17 | link:https://github.com/jenkinsci/http-request-plugin/releases/latest[image:https://img.shields.io/github/release/jenkinsci/http-request-plugin.svg?label=changelog[GitHub release]] 18 | 19 | toc::[] 20 | 21 | [abstract] 22 | .Overview 23 | This plugin sends a HTTP/HTTPS request to a user specified URL. The request is made via job 24 | execution in Jenkins and depending on the HTTP response the job can be marked as failed 25 | (configurable). For example, responses such as 404 and 500 can make the job fail. When a 26 | job fails it will log the response to help identify the problem. 27 | 28 | According to the setting of HTTP mode the request will be performed either using HTTP GET or POST. 29 | If there is no such setting then it will use the default from global settings. Default there is POST. 30 | 31 | == Features 32 | 33 | The following powerful features are available in both Pipeline and traditional project types, giving you greater control and flexibility over your builds: 34 | 35 | * Programmable HTTP method: 36 | Choose from a variety of HTTP methods, including GET, POST, MKCOL, PUT, PATCH, DELETE, OPTIONS, or HEAD, to suit your project's specific needs. 37 | 38 | * Programmable range of expected response codes: 39 | Specify a range of expected response codes for your build, and if the response code falls outside the specified range, the build will fail, saving you time and hassle. 40 | 41 | * Supports Basic Authentication: 42 | Use Basic Authentication to ensure that only authorized users can access your project's resources, providing an additional layer of security. 43 | 44 | * Supports Form Authentication: 45 | Form Authentication enables users to authenticate themselves by submitting a username and password through a form, ensuring that only authorized users can access your resources. 46 | 47 | * Supports Certificate-based Authentication: 48 | Use a certificate from a Jenkins stored credential to authenticate your HTTPS requests to a remote server. 49 | 50 | * Specify a required string in the response: 51 | Ensure that a specific string is present in the response by specifying it beforehand. If the string is not present, the build will fail, alerting you to the issue. 52 | 53 | * Set a connection timeout limit: 54 | Prevent builds from taking too long by setting a connection timeout limit. If the limit is exceeded, the build will fail, saving you time and resources. 55 | 56 | * Set an "Accept" header directly: 57 | Set the "Accept" header directly, providing greater control over the type of data that the server returns in response to a request. 58 | 59 | * Set a "Content-Type" header directly: 60 | Set the "Content-Type" header directly, specifying the type of data that you are sending in your request, helping to ensure that the server can correctly process your request. 61 | 62 | * Set any custom header: 63 | Set any custom header that you require, enabling you to interact with APIs or web services that require specific headers or authentication protocols. 64 | 65 | === Basic plugin features 66 | 67 | [NOTE] 68 | .Feature Availability 69 | ==== 70 | The following features are only present in the non-pipeline version of the plugin. For the Pipeline 71 | version, these features are available programmatically. 72 | ==== 73 | 74 | * You can send the build parameters as URL query strings 75 | * You can store the response to a file, built-in to the plugin 76 | 77 | === Pipeline features 78 | 79 | In a Pipeline job, you have total control over how the url is formed. Suppose you have a build 80 | parameter called "`param1`", you can pass it to the HTTP request programmatically like so: 81 | 82 | [source,groovy] 83 | ---- 84 | httpRequest "http://httpbin.org/response-headers?param1=${param1}" 85 | ---- 86 | 87 | If you wish to save the response to a file, you need to grab a workspace. You can do this with a 88 | `node` Pipeline step. For example: 89 | 90 | [source,groovy] 91 | ---- 92 | def response = httpRequest "http://httpbin.org/response-headers?param1=${param1}" 93 | node() { 94 | writeFile file: 'response.txt', text: response.content 95 | } 96 | ---- 97 | 98 | You can access the response status code, content and headers programmatically: 99 | 100 | [source,groovy] 101 | ---- 102 | def response = httpRequest "http://httpbin.org/response-headers?param1=${param1}" 103 | println("Status: ${response.status}") 104 | println("Response: ${response.content}") 105 | println("Headers: ${response.headers}") 106 | ---- 107 | 108 | You may also send content in the body of the request, such as for a PATCH request: 109 | 110 | [source,groovy] 111 | ---- 112 | // create payload 113 | def patchOrg = """ 114 | {"description": "$description"} 115 | """ 116 | def response = httpRequest acceptType: 'APPLICATION_JSON', contentType: 'APPLICATION_JSON', 117 | httpMode: 'PATCH', requestBody: patchOrg, 118 | url: "https://api.github.com/orgs/${orgName}" 119 | ---- 120 | 121 | You may also send content in the body of the request, such as for a POST request: 122 | 123 | [source,groovy] 124 | ---- 125 | httpRequest acceptType: 'APPLICATION_JSON', contentType: 'APPLICATION_JSON', 126 | httpMode: 'POST', quiet: true, 127 | requestBody: '''{ 128 | "display-name" : "my_Username", 129 | "email" : "user@example.test", 130 | "password" : { 131 | "value" : "my_password" 132 | }, 133 | }''', 134 | url: 'https://api.github.com/orgs/${orgName}' 135 | ---- 136 | 137 | 138 | You can also set custom headers: 139 | 140 | [source,groovy] 141 | ---- 142 | def response = httpRequest customHeaders: [[name: 'foo', value: 'bar']] 143 | ---- 144 | 145 | You can also set custom headers with mask set true: 146 | 147 | [source,groovy] 148 | ---- 149 | def response = httpRequest customHeaders: [[maskValue: true, name: 'foo', value: 'bar']], 150 | url: 'https://api.github.com/orgs/${orgName}' 151 | ---- 152 | 153 | You can send ``multipart/form-data`` forms: 154 | 155 | [source,groovy] 156 | ---- 157 | def response = httpRequest httpMode: 'POST', formData: [ 158 | [contentType: 'application/json', name: 'model', body: '{"foo": "bar"}'], 159 | [contentType: 'text/plain', name: 'file', fileName: 'readme.txt', 160 | uploadFile: 'data/lipsum.txt']] 161 | ---- 162 | 163 | You can send a request with form-data: 164 | 165 | [source,groovy] 166 | ---- 167 | def response = httpRequest acceptType: 'APPLICATION_JSON', contentType: 'APPLICATION_FORM_DATA', 168 | formData: [[body: '''{ 169 | "name" : "example", 170 | "type" : "bot" 171 | }''', 172 | contentType: 'text/plain', fileName: 'sample', name: 'data', 173 | uploadFile: './files/readme.txt']], 174 | httpMode: 'POST', quiet: true, responseHandle: 'NONE', timeout: null, 175 | url: 'https://api.github.com/orgs/${orgName}', 176 | validResponseCodes: '200,404', validResponseContent: 'token' 177 | ---- 178 | 179 | You can send ``multipart file`` and ``multipart entity name``: 180 | 181 | [source,groovy] 182 | ---- 183 | def response = httpRequest acceptType: 'APPLICATION_JSON', contentType: 'APPLICATION_OCTETSTREAM', 184 | httpMode: 'POST', multipartName: 'file', quiet: true, 185 | responseHandle: 'NONE', timeout: null, uploadFile: './files/readme.txt', 186 | url: 'https://api.github.com/orgs/${orgName}' 187 | ---- 188 | 189 | You can send a request with SSL error ignored 190 | 191 | [source,groovy] 192 | ---- 193 | def response = httpRequest ignoreSslErrors: true, responseHandle: 'NONE', 194 | url: 'https://api.github.com/orgs/${orgName}' 195 | ---- 196 | 197 | You can send a request with http proxy 198 | 199 | [source,groovy] 200 | ---- 201 | def response = httpRequest httpProxy: 'http://proxy.local', responseHandle: 'NONE', 202 | url: 'https://api.github.com/orgs/${orgName}' 203 | ---- 204 | 205 | You can send a request with http proxy authenticate 206 | 207 | [source,groovy] 208 | ---- 209 | def response = httpRequest proxyAuthentication: Basic, 'http://proxy.local', 210 | responseHandle: 'NONE', url: 'https://api.github.com/orgs/${orgName}' 211 | ---- 212 | 213 | You can send a request with accepted response codes 214 | 215 | [source,groovy] 216 | ---- 217 | def response = httpRequest responseHandle: 'NONE', validResponseCodes: '200,404', 218 | url: 'https://api.github.com/orgs/${orgName}' 219 | ---- 220 | 221 | You can send a request with accepted response content 222 | 223 | [source,groovy] 224 | ---- 225 | def response = httpRequest responseHandle: 'STRING', 226 | url: 'https://api.github.com/orgs/${orgName}', 227 | validResponseCodes: '200,404', validResponseContent: 'token' 228 | ---- 229 | 230 | You can send a request with connection timeout 231 | 232 | [source,groovy] 233 | ---- 234 | def response = httpRequest timeout: 30, url: 'https://api.github.com/orgs/${orgName}' 235 | ---- 236 | 237 | You can send a request where output is written to file 238 | 239 | [source,groovy] 240 | ---- 241 | def response = httpRequest outputFile: 'readme.txt', url:'https://api.github.com/orgs/${orgName}' 242 | ---- 243 | 244 | You can send a request where response is printed on the console 245 | 246 | [source,groovy] 247 | ---- 248 | def response = httpRequest consoleLogResponseBody: true, 249 | url:'https://api.github.com/orgs/${orgName}' 250 | ---- 251 | 252 | You can send a request without logging output — with logs turned off 253 | 254 | [source,groovy] 255 | ---- 256 | def response = httpRequest quiet: true, url:'https://api.github.com/orgs/${orgName}' 257 | ---- 258 | 259 | You can handle response 260 | 261 | [source,groovy] 262 | ---- 263 | def response = httpRequest responseHandle: 'LEAVE_OPEN', 264 | url: "https://api.github.com/orgs/${orgName}" 265 | response.close() // must call response.close() after a LEAVE_OPEN 266 | ---- 267 | 268 | You can use a Jenkins credential to authenticate the request 269 | 270 | [source,groovy] 271 | ---- 272 | def response = httpRequest authentication: 'my-jenkins-credential-id', 273 | url: 'https://api.github.com/user/jenkinsci' 274 | ---- 275 | 276 | You can send an SSL request with authentication by user certificate; 277 | for a private CA, make sure to first add the CA certificate is as 278 | "Trusted", then add the user key along with certification chain up 279 | to same CA certificate, into your PKCS12 keystore file which you 280 | upload to Jenkins credentials, and you also must use a non-trivial 281 | password for that keystore. Keep in mind that for systems under test 282 | which create their own self-signed CA and HTTPS protection, you can 283 | programmatically create and upload the credentials, into a domain 284 | where the job has write access (its folder etc.) 285 | 286 | [source,groovy] 287 | ---- 288 | def response = httpRequest authentication: 'user_with_cert_and_ca', 289 | url: 'https://sut123.local.domain:8443/api/v1/status/debug' 290 | ---- 291 | 292 | A basic WebDAV upload can be built using ``MKCOL`` and ``PUT`` like so: 293 | 294 | [source,groovy] 295 | ---- 296 | // create directory aka a collection 297 | httpRequest authentication: 'my-jenkins-credential-id', 298 | httpMode: 'MKCOL', 299 | // on Apache httpd 201 = collection created, 405 = collection already exists 300 | validResponseCodes: '201,405', 301 | url: "https://example.com/webdav-enabled-server/reports/${version}/" 302 | // upload a file 303 | httpRequest authentication: 'my-jenkins-credential-id', 304 | httpMode: 'PUT', 305 | validResponseCodes: '201', 306 | url: "https://example.com/reports/${version}/your-report-maybe.html", 307 | uploadFile: './local/path/to/report.html' 308 | ---- 309 | 310 | For details on the Pipeline features, use the Pipeline snippet generator in the Pipeline job 311 | configuration. 312 | 313 | [WARNING] 314 | .Known Limitations 315 | ==== 316 | If Jenkins is restarted before the HTTP response comes back, the build will fail. 317 | ==== 318 | 319 | == Building 320 | 321 | The plugin can be built and tested locally using a Maven Docker container: 322 | 323 | [source, bash] 324 | ---- 325 | docker run -it --rm -v "$(pwd)":/usr/src/mymaven -w /usr/src/mymaven maven:3.3-jdk-8 mvn test 326 | ---- 327 | 328 | == Configure Global Settings 329 | 330 | image::docs/images/configure-http-request-global.png[] 331 | 332 | == Configure Build Step in your Jenkins job 333 | 334 | image::docs/images/configure-http-request-build-step.png[] 335 | 336 | == HTTP Request Parameters 337 | 338 | Parameters are escaped, which means if you try to pass another value inside a value, it will not 339 | happen. 340 | 341 | In the example below, the key "`name`" will be passed with a value of "`jenkins&os=linux`". Note 342 | that "`os`" is not a parameter - it is part of the value). At the HTTP server-side no parameter 343 | named "`os`" will exist. 344 | 345 | [CAUTION] 346 | .Regarding Logging & Sensitive Information 347 | ==== 348 | Every execution will log all parameters. Be careful to not pass private information such as 349 | passwords or personal information. 350 | ==== 351 | 352 | image:docs/images/log.png[] 353 | 354 | == Issues 355 | 356 | Report issues and enhancements in the https://issues.jenkins.io/[Jenkins issue tracker]. 357 | Use the `http-request-plugin` component in the `JENKINS` project. 358 | 359 | == Contributing 360 | 361 | Refer to our https://github.com/jenkinsci/.github/blob/master/CONTRIBUTING.md[contribution guidelines]. 362 | 363 | == License 364 | 365 | Licensed under link:LICENSE[the MIT License]. 366 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/http_request/HttpRequestStepCredentialsTest.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.*; 5 | 6 | import com.cloudbees.plugins.credentials.CredentialsProvider; 7 | import com.cloudbees.plugins.credentials.CredentialsScope; 8 | import com.cloudbees.plugins.credentials.CredentialsStore; 9 | import com.cloudbees.plugins.credentials.SecretBytes; 10 | import com.cloudbees.plugins.credentials.SystemCredentialsProvider; 11 | import com.cloudbees.plugins.credentials.common.StandardCredentials; 12 | import com.cloudbees.plugins.credentials.domains.Domain; 13 | import com.cloudbees.plugins.credentials.impl.CertificateCredentialsImpl; 14 | import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; 15 | import hudson.model.Descriptor.FormException; 16 | import hudson.model.Fingerprint; 17 | import hudson.model.Result; 18 | 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.nio.file.Files; 23 | import java.util.Collections; 24 | import jenkins.model.Jenkins; 25 | 26 | import org.apache.commons.io.FileUtils; 27 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 28 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 29 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 30 | 31 | import org.htmlunit.html.HtmlPage; 32 | import org.junit.jupiter.api.BeforeEach; 33 | import org.junit.jupiter.api.Test; 34 | import org.junit.jupiter.api.io.TempDir; 35 | import org.jvnet.hudson.test.Issue; 36 | import org.jvnet.hudson.test.JenkinsRule; 37 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 38 | 39 | /** 40 | * @author Mark Waite 41 | */ 42 | @WithJenkins 43 | class HttpRequestStepCredentialsTest extends HttpRequestTestBase { 44 | // For developers: set to `true` so that pipeline console logs show 45 | // up in System.out (and/or System.err) of the plugin test run by 46 | // mvn test -Dtest="HttpRequestStepCredentialsTest" 47 | private final boolean verbosePipelines = false; 48 | 49 | private String getLogAsStringPlaintext(WorkflowRun f) throws java.io.IOException { 50 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 51 | f.getLogText().writeLogTo(0, baos); 52 | return baos.toString(); 53 | } 54 | 55 | // From CertificateCredentialImplTest 56 | @TempDir 57 | private File tmp; 58 | private File p12simple; 59 | private File p12trusted; 60 | 61 | private CredentialsStore store = null; 62 | 63 | private static StandardCredentials getInvalidCredential() throws FormException { 64 | String username = "bad-user"; 65 | String password = "bad-password"; 66 | CredentialsScope scope = CredentialsScope.GLOBAL; 67 | String id = "username-" + username + "-password-" + password; 68 | return new UsernamePasswordCredentialsImpl(scope, id, "desc: " + id, username, password); 69 | } 70 | 71 | private StandardCredentials getCertificateCredentialSimple() throws IOException { 72 | if (p12simple == null) { 73 | // Contains a private key + openvpn certs, 74 | // as alias named "1" (according to keytool) 75 | p12simple = File.createTempFile("test.p12", null, tmp); 76 | FileUtils.copyURLToFile(HttpRequestStepCredentialsTest.class.getResource("test.p12"), p12simple); 77 | } 78 | 79 | SecretBytes uploadedKeystore = SecretBytes.fromRawBytes(Files.readAllBytes(p12simple.toPath())); 80 | CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(null, uploadedKeystore); 81 | return new CertificateCredentialsImpl(null, "cred_cert_simple", null, "password", storeSource); 82 | } 83 | 84 | private StandardCredentials getCertificateCredentialTrusted() throws IOException { 85 | if (p12trusted == null) { 86 | // Contains a private key + openvpn certs as alias named "1", 87 | // and another alias named "ca" with trustedKeyEntry for CA 88 | p12trusted = File.createTempFile("testTrusted.p12", null, tmp); 89 | FileUtils.copyURLToFile(HttpRequestStepCredentialsTest.class.getResource("testTrusted.p12"), p12trusted); 90 | } 91 | 92 | SecretBytes uploadedKeystore = SecretBytes.fromRawBytes(Files.readAllBytes(p12trusted.toPath())); 93 | CertificateCredentialsImpl.UploadedKeyStoreSource storeSource = new CertificateCredentialsImpl.UploadedKeyStoreSource(null, uploadedKeystore); 94 | return new CertificateCredentialsImpl(null, "cred_cert_with_ca", null, "password", storeSource); 95 | } 96 | 97 | @BeforeEach 98 | void enableSystemCredentialsProvider() { 99 | SystemCredentialsProvider.getInstance() 100 | .setDomainCredentialsMap( 101 | Collections.singletonMap(Domain.global(), Collections.emptyList())); 102 | for (CredentialsStore s : CredentialsProvider.lookupStores(Jenkins.get())) { 103 | if (s.getProvider() instanceof SystemCredentialsProvider.ProviderImpl) { 104 | store = s; 105 | break; 106 | } 107 | } 108 | assertThat("The system credentials provider is enabled", store, notNullValue()); 109 | } 110 | 111 | @Test 112 | void trackCredentials() throws Exception { 113 | StandardCredentials credential = getInvalidCredential(); 114 | store.addCredentials(Domain.global(), credential); 115 | 116 | Fingerprint fingerprint = CredentialsProvider.getFingerprintOf(credential); 117 | assertThat("Fingerprint should not be set before job definition", fingerprint, nullValue()); 118 | 119 | JenkinsRule.WebClient wc = j.createWebClient(); 120 | HtmlPage page = wc.goTo("credentials/store/system/domain/_/credentials/" + credential.getId()); 121 | assertThat("Have usage tracking reported", page.getElementById("usage"), notNullValue()); 122 | assertThat( 123 | "No fingerprint created until first use on missing page", 124 | page.getElementById("usage-missing"), 125 | notNullValue()); 126 | assertThat( 127 | "No fingerprint created until first use on present page", 128 | page.getElementById("usage-present"), 129 | nullValue()); 130 | 131 | // Configure the build to use the credential 132 | WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); 133 | proj.setDefinition( 134 | new CpsFlowDefinition( 135 | "def response = httpRequest(url: 'https://api.github.com/users/jenkinsci',\n" 136 | + " authentication: '" + credential.getId() + "')\n" 137 | + "println('Status: '+response.getStatus())\n" 138 | + "println('Response: '+response.getContent())\n", 139 | true)); 140 | 141 | fingerprint = CredentialsProvider.getFingerprintOf(credential); 142 | assertThat("Fingerprint should not be set before first build", fingerprint, nullValue()); 143 | 144 | // Execute the build 145 | WorkflowRun run = proj.scheduleBuild2(0).get(); 146 | 147 | // Check expectations 148 | j.assertBuildStatus(Result.SUCCESS, run); 149 | j.assertLogContains("https://api.github.com/users/jenkinsci/followers", run); 150 | 151 | // Check the credential use was correctly tracked 152 | fingerprint = CredentialsProvider.getFingerprintOf(credential); 153 | assertThat("Fingerprint should be set after first build", fingerprint, notNullValue()); 154 | assertThat(fingerprint.getJobs(), hasItem(is(proj.getFullName()))); 155 | Fingerprint.RangeSet rangeSet = fingerprint.getRangeSet(proj); 156 | assertThat(rangeSet, notNullValue()); 157 | assertThat(rangeSet.includes(proj.getLastBuild().getNumber()), is(true)); 158 | 159 | page = wc.goTo("credentials/store/system/domain/_/credentials/" + credential.getId()); 160 | assertThat(page.getElementById("usage-missing"), nullValue()); 161 | assertThat(page.getElementById("usage-present"), notNullValue()); 162 | assertThat(page.getAnchorByText(proj.getFullDisplayName()), notNullValue()); 163 | } 164 | 165 | // A set of tests with certificate credentials in different contexts 166 | // TODO: Test on remote agent as in https://github.com/jenkinsci/credentials-plugin/pull/391 167 | // but this requires that PR to be merged first, so credentials-plugin 168 | // processes snapshot() and readable keystore data gets to remote agent. 169 | // Note that the tests below focus on ability of the plugin to load and 170 | // process the key store specified by the credential, rather than that 171 | // it is usable further. It would be a separate effort to mock up a web 172 | // server protected by HTTPS and using certificates for login (possibly 173 | // user and server backed by two different CA's), and query that. 174 | private static String cpsScriptCredentialTestHttpRequest(String id, String runnerTag) { 175 | // Note: we accept any outcome (for the plugin, unresolved host is HTTP-404) 176 | // but it may not crash making use of the credential 177 | // Note: cases withLocalCertLookup also need cpsScriptCredentialTestImports() 178 | return "def authentication='" + id + "';\n" 179 | + "\n" 180 | + "echo \"Querying HTTPS with credential...\"\n" 181 | + "def response = httpRequest(url: 'https://github.xcom/api/v3',\n" 182 | + " httpMode: 'GET',\n" 183 | + " authentication: authentication,\n" 184 | + " consoleLogResponseBody: true,\n" 185 | + " contentType : 'APPLICATION_FORM',\n" 186 | + " validResponseCodes: '100:599',\n" 187 | + " quiet: false)\n" 188 | + "println('First HTTP Request Plugin Status: '+ response.getStatus())\n" 189 | + "println('First HTTP Request Plugin Response: '+ response.getContent())\n" 190 | + "\n" 191 | + "echo \"Querying HTTPS with credential again (reentrability)...\"\n" 192 | + "response = httpRequest(url: 'https://github.xcom/api/v3',\n" 193 | + " httpMode: 'GET',\n" 194 | + " authentication: authentication,\n" 195 | + " consoleLogResponseBody: true,\n" 196 | + " contentType : 'APPLICATION_FORM',\n" 197 | + " validResponseCodes: '100:599',\n" 198 | + " quiet: false)\n" 199 | + "println('Second HTTP Request Plugin Status: '+ response.getStatus())\n" 200 | + "println('Second HTTP Request Plugin Response: '+ response.getContent())\n" 201 | + "\n"; 202 | } 203 | 204 | @Test 205 | @Issue({"JENKINS-70000", "JENKINS-70101"}) 206 | void testCertSimpleHttpRequestOnController() throws Exception { 207 | // Check that credentials are usable with pipeline script 208 | // running without a node{} 209 | StandardCredentials credential = getCertificateCredentialSimple(); 210 | store.addCredentials(Domain.global(), credential); 211 | 212 | // Configure the build to use the credential 213 | WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); 214 | String script = 215 | cpsScriptCredentialTestHttpRequest("cred_cert_simple", "CONTROLLER BUILT-IN"); 216 | proj.setDefinition(new CpsFlowDefinition(script, false)); 217 | 218 | // Execute the build 219 | WorkflowRun run = proj.scheduleBuild2(0).get(); 220 | if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); 221 | 222 | // Check expectations 223 | j.assertBuildStatus(Result.SUCCESS, run); 224 | // Got to the end? 225 | j.assertLogContains("HTTP Request Plugin Response: ", run); 226 | j.assertLogContains("Using authentication: cred_cert_simple", run); 227 | // Currently we always try adding the material 228 | // and report if not failed trying (might have 229 | // had 0 entries to add though): 230 | //j.assertLogNotContains("Added Trust Material from provided KeyStore", run); 231 | j.assertLogContains("Added Key Material from provided KeyStore", run); 232 | j.assertLogContains("Treating UnknownHostException", run); 233 | } 234 | 235 | @Test 236 | @Issue({"JENKINS-70000", "JENKINS-70101"}) 237 | void testCertSimpleHttpRequestOnNodeLocal() throws Exception { 238 | // Check that credentials are usable with pipeline script 239 | // running on a node{} (provided by the controller) 240 | StandardCredentials credential = getCertificateCredentialSimple(); 241 | store.addCredentials(Domain.global(), credential); 242 | 243 | // Configure the build to use the credential 244 | WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); 245 | String script = 246 | "node {\n" + 247 | cpsScriptCredentialTestHttpRequest("cred_cert_simple", "CONTROLLER NODE") + 248 | "}\n"; 249 | proj.setDefinition(new CpsFlowDefinition(script, false)); 250 | 251 | // Execute the build 252 | WorkflowRun run = proj.scheduleBuild2(0).get(); 253 | if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); 254 | 255 | // Check expectations 256 | j.assertBuildStatus(Result.SUCCESS, run); 257 | // Got to the end? 258 | j.assertLogContains("HTTP Request Plugin Response: ", run); 259 | j.assertLogContains("Using authentication: cred_cert_simple", run); 260 | // Currently we always try adding the material 261 | // and report if not failed trying (might have 262 | // had 0 entries to add though): 263 | //j.assertLogNotContains("Added Trust Material from provided KeyStore", run); 264 | j.assertLogContains("Added Key Material from provided KeyStore", run); 265 | j.assertLogContains("Treating UnknownHostException", run); 266 | } 267 | 268 | @Test 269 | @Issue({"JENKINS-70000", "JENKINS-70101"}) 270 | void testCertTrustedHttpRequestOnController() throws Exception { 271 | // Check that credentials are usable with pipeline script 272 | // running without a node{} 273 | StandardCredentials credential = getCertificateCredentialTrusted(); 274 | store.addCredentials(Domain.global(), credential); 275 | 276 | // Configure the build to use the credential 277 | WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); 278 | String script = 279 | cpsScriptCredentialTestHttpRequest("cred_cert_with_ca", "CONTROLLER BUILT-IN"); 280 | proj.setDefinition(new CpsFlowDefinition(script, false)); 281 | 282 | // Execute the build 283 | WorkflowRun run = proj.scheduleBuild2(0).get(); 284 | if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); 285 | 286 | // Check expectations 287 | j.assertBuildStatus(Result.SUCCESS, run); 288 | // Got to the end? 289 | j.assertLogContains("HTTP Request Plugin Response: ", run); 290 | j.assertLogContains("Using authentication: cred_cert_with_ca", run); 291 | j.assertLogContains("Added Trust Material from provided KeyStore", run); 292 | j.assertLogContains("Added Key Material from provided KeyStore", run); 293 | j.assertLogContains("Treating UnknownHostException", run); 294 | } 295 | 296 | @Test 297 | @Issue({"JENKINS-70000", "JENKINS-70101"}) 298 | void testCertTrustedHttpRequestOnNodeLocal() throws Exception { 299 | // Check that credentials are usable with pipeline script 300 | // running on a node{} (provided by the controller) 301 | StandardCredentials credential = getCertificateCredentialTrusted(); 302 | store.addCredentials(Domain.global(), credential); 303 | 304 | // Configure the build to use the credential 305 | WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proj"); 306 | String script = 307 | "node {\n" + 308 | cpsScriptCredentialTestHttpRequest("cred_cert_with_ca", "CONTROLLER NODE") + 309 | "}\n"; 310 | proj.setDefinition(new CpsFlowDefinition(script, false)); 311 | 312 | // Execute the build 313 | WorkflowRun run = proj.scheduleBuild2(0).get(); 314 | if (verbosePipelines) System.out.println(getLogAsStringPlaintext(run)); 315 | 316 | // Check expectations 317 | j.assertBuildStatus(Result.SUCCESS, run); 318 | // Got to the end? 319 | j.assertLogContains("HTTP Request Plugin Response: ", run); 320 | j.assertLogContains("Using authentication: cred_cert_with_ca", run); 321 | j.assertLogContains("Added Trust Material from provided KeyStore", run); 322 | j.assertLogContains("Added Key Material from provided KeyStore", run); 323 | j.assertLogContains("Treating UnknownHostException", run); 324 | } 325 | 326 | } 327 | -------------------------------------------------------------------------------- /src/test/java/jenkins/plugins/http_request/Registers.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.Base64; 9 | import java.util.Enumeration; 10 | import java.util.Map; 11 | 12 | import jakarta.servlet.ServletException; 13 | 14 | import org.apache.hc.core5.http.ContentType; 15 | import org.apache.hc.core5.http.HttpHeaders; 16 | import org.eclipse.jetty.http.HttpHeader; 17 | import org.eclipse.jetty.http.HttpStatus; 18 | import org.eclipse.jetty.http.MultiPart; 19 | import org.eclipse.jetty.http.MultiPartConfig; 20 | import org.eclipse.jetty.http.MultiPartFormData; 21 | import org.eclipse.jetty.server.Request; 22 | import org.eclipse.jetty.server.Response; 23 | import org.eclipse.jetty.util.Callback; 24 | 25 | import jenkins.plugins.http_request.HttpRequestTestBase.SimpleHandler; 26 | 27 | /** 28 | * @author Janario Oliveira 29 | */ 30 | class Registers { 31 | 32 | static void registerRequestChecker(final HttpMode method) { 33 | registerHandler("/do" + method.name(), method, new SimpleHandler() { 34 | @Override 35 | boolean doHandle(Request request, Response response, Callback callback) { 36 | assertEquals(method.name(), request.getMethod()); 37 | 38 | String query = request.getHttpURI().getQuery(); 39 | assertNull(query); 40 | return okAllIsWell(response, callback); 41 | } 42 | }); 43 | } 44 | 45 | static void registerContentTypeRequestChecker(final MimeType mimeType, final HttpMode httpMode, final String responseMessage) { 46 | registerHandler("/incoming_" + mimeType.toString(), httpMode, new SimpleHandler() { 47 | @Override 48 | boolean doHandle(Request request, Response response, Callback callback) throws IOException { 49 | assertEquals(httpMode.name(), request.getMethod()); 50 | 51 | Enumeration headers = request.getHeaders().getValues(HttpHeaders.CONTENT_TYPE); 52 | if (mimeType == MimeType.NOT_SET) { 53 | assertFalse(headers.hasMoreElements()); 54 | } else { 55 | assertTrue(headers.hasMoreElements()); 56 | String actual = headers.nextElement(); 57 | assertFalse(headers.hasMoreElements()); 58 | 59 | // ContentType charset value changed from UTF-8 to utf-8 in Jetty 12.1 60 | // Use a case insensitive comparison of the Content-Type header 61 | // https://www.rfc-editor.org/rfc/rfc9110#name-charset - charset names are matched case-insensitively 62 | String expected = mimeType.getContentType().toString(); 63 | assertTrue(expected.equalsIgnoreCase(actual), "Content-Type mismatch, expected '" + expected + "', but was '" + actual + "'"); 64 | } 65 | 66 | String query = request.getHttpURI().getQuery(); 67 | assertNull(query); 68 | String body = responseMessage != null ? responseMessage : requestBody(request); 69 | return body(response, HttpStatus.OK_200, mimeType.getContentType(), body, callback); 70 | } 71 | }); 72 | } 73 | 74 | static void registerAcceptedTypeRequestChecker(final MimeType mimeType) { 75 | registerHandler("/accept_" + mimeType.toString(), HttpMode.GET, new SimpleHandler() { 76 | @Override 77 | boolean doHandle(Request request, Response response, Callback callback) { 78 | assertEquals("GET", request.getMethod()); 79 | 80 | Enumeration headers = request.getHeaders().getValues(HttpHeaders.ACCEPT); 81 | 82 | 83 | if (mimeType == MimeType.NOT_SET) { 84 | assertFalse(headers.hasMoreElements()); 85 | } else { 86 | assertTrue(headers.hasMoreElements()); 87 | String value = headers.nextElement(); 88 | assertFalse(headers.hasMoreElements()); 89 | 90 | assertEquals(mimeType.getValue(), value); 91 | } 92 | String query = request.getHttpURI().getQuery(); 93 | assertNull(query); 94 | return okAllIsWell(response, callback); 95 | } 96 | }); 97 | } 98 | 99 | static void registerTimeout() { 100 | // Timeout, do not respond! 101 | registerHandler("/timeout", HttpMode.GET, new SimpleHandler() { 102 | @Override 103 | boolean doHandle(Request request, Response response, Callback callback) { 104 | try { 105 | Thread.sleep(10000); 106 | } catch (InterruptedException ex) { 107 | // do nothing the sleep will be interrupted when the test ends 108 | } 109 | return true; 110 | } 111 | }); 112 | } 113 | 114 | static void registerReqAction() { 115 | // Accept the form authentication 116 | registerHandler("/reqAction", HttpMode.GET, new SimpleHandler() { 117 | @Override 118 | boolean doHandle(Request request, Response response, Callback callback) throws ServletException { 119 | assertEquals("GET", request.getMethod()); 120 | 121 | Map parameters; 122 | try { 123 | parameters = Request.getParameters(request).toStringArrayMap(); 124 | } catch (Exception e) { 125 | throw new ServletException(e); 126 | } 127 | 128 | assertEquals(2, parameters.size()); 129 | assertTrue(parameters.containsKey("param1")); 130 | String[] value = parameters.get("param1"); 131 | assertEquals(1, value.length); 132 | assertEquals("value1", value[0]); 133 | 134 | assertTrue(parameters.containsKey("param2")); 135 | value = parameters.get("param2"); 136 | assertEquals(1, value.length); 137 | assertEquals("value2", value[0]); 138 | return okAllIsWell(response, callback); 139 | } 140 | }); 141 | } 142 | 143 | static void registerFormAuth() { 144 | // Check the form authentication 145 | registerHandler("/formAuth", HttpMode.GET, new SimpleHandler() { 146 | @Override 147 | boolean doHandle(Request request, Response response, Callback callback) { 148 | return okAllIsWell(response, callback); 149 | } 150 | }); 151 | } 152 | 153 | static void registerFormAuthBad() { 154 | // Check the form authentication header 155 | registerHandler("/formAuthBad", HttpMode.GET, new SimpleHandler() { 156 | @Override 157 | boolean doHandle(Request request, Response response, Callback callback) { 158 | Response.writeError(request, response, callback, HttpStatus.BAD_REQUEST_400, "Not allowed"); 159 | return true; 160 | } 161 | }); 162 | } 163 | 164 | static void registerBasicAuth() { 165 | // Check the basic authentication header 166 | registerHandler("/basicAuth", HttpMode.GET, new SimpleHandler() { 167 | @Override 168 | boolean doHandle(Request request, Response response, Callback callback) { 169 | Enumeration headers = request.getHeaders().getValues(HttpHeaders.AUTHORIZATION); 170 | 171 | String value = headers.nextElement(); 172 | assertFalse(headers.hasMoreElements()); 173 | 174 | byte[] bytes = Base64.getDecoder().decode(value.substring(6)); 175 | String usernamePasswordPair = new String(bytes); 176 | String[] usernamePassword = usernamePasswordPair.split(":"); 177 | assertEquals("username1", usernamePassword[0]); 178 | assertEquals("password1", usernamePassword[1]); 179 | 180 | return okAllIsWell(response, callback); 181 | } 182 | }); 183 | } 184 | 185 | static void registerCheckRequestBodyWithTag() { 186 | // Check that request body is present and that the containing parameter ${Tag} has been resolved to "trunk" 187 | registerHandler("/checkRequestBodyWithTag", HttpMode.POST, new SimpleHandler() { 188 | @Override 189 | boolean doHandle(Request request, Response response, Callback callback) throws IOException { 190 | assertEquals("POST", request.getMethod()); 191 | String requestBody = requestBody(request); 192 | 193 | assertEquals("cleanupDir=D:/continuousIntegration/deployments/Daimler/trunk/standalone", requestBody); 194 | return okAllIsWell(response, callback); 195 | } 196 | }); 197 | } 198 | 199 | static void registerCustomHeaders() { 200 | // Check the custom headers 201 | registerHandler("/customHeaders", HttpMode.GET, new SimpleHandler() { 202 | @Override 203 | boolean doHandle(Request request, Response response, Callback callback) { 204 | Enumeration headers = request.getHeaders().getValues("customHeader"); 205 | 206 | String value1 = headers.nextElement(); 207 | String value2 = headers.nextElement(); 208 | 209 | assertFalse(headers.hasMoreElements()); 210 | assertEquals("value1", value1); 211 | assertEquals("value2", value2); 212 | 213 | return okAllIsWell(response, callback); 214 | } 215 | }); 216 | } 217 | 218 | static void registerInvalidStatusCode() { 219 | // Return an invalid status code 220 | registerHandler("/invalidStatusCode", HttpMode.GET, new SimpleHandler() { 221 | @Override 222 | boolean doHandle(Request request, Response response, Callback callback) { 223 | assertEquals("GET", request.getMethod()); 224 | String query = request.getHttpURI().getQuery(); 225 | assertNull(query); 226 | 227 | Response.writeError(request, response, callback, HttpStatus.BAD_REQUEST_400, "Throwing status 400 for test"); 228 | return true; 229 | } 230 | }); 231 | } 232 | 233 | static void registerCustomHeadersResolved() { 234 | // Check if the parameters in custom headers have been resolved 235 | registerHandler("/customHeadersResolved", HttpMode.POST, new SimpleHandler() { 236 | @Override 237 | boolean doHandle(Request request, Response response, Callback callback) { 238 | Enumeration headers = request.getHeaders().getValues("resolveCustomParam"); 239 | 240 | String value = headers.nextElement(); 241 | assertFalse(headers.hasMoreElements()); 242 | assertEquals("trunk", value); 243 | 244 | headers = request.getHeaders().getValues("resolveEnvParam"); 245 | value = headers.nextElement(); 246 | assertFalse(headers.hasMoreElements()); 247 | assertEquals("C:/path/to/my/workspace", value); 248 | 249 | return okAllIsWell(response, callback); 250 | } 251 | }); 252 | } 253 | 254 | static void registerCheckBuildParameters() { 255 | // Check that exactly one build parameter is passed 256 | registerHandler("/checkBuildParameters", HttpMode.GET, new SimpleHandler() { 257 | @Override 258 | boolean doHandle(Request request, Response response, Callback callback) throws ServletException { 259 | assertEquals("GET", request.getMethod()); 260 | 261 | Map parameters; 262 | try { 263 | parameters = Request.getParameters(request).toStringArrayMap(); 264 | } catch (Exception e) { 265 | throw new ServletException(e); 266 | } 267 | 268 | assertEquals(1, parameters.size()); 269 | assertTrue(parameters.containsKey("foo")); 270 | String[] value = parameters.get("foo"); 271 | assertEquals(1, value.length); 272 | assertEquals("value", value[0]); 273 | 274 | return okAllIsWell(response, callback); 275 | } 276 | }); 277 | } 278 | 279 | static void registerCheckRequestBody() { 280 | // Check that request body is present and equals to TestRequestBody 281 | registerHandler("/checkRequestBody", HttpMode.POST, new SimpleHandler() { 282 | @Override 283 | boolean doHandle(Request request, Response response, Callback callback) throws IOException { 284 | assertEquals("POST", request.getMethod()); 285 | String requestBody = requestBody(request); 286 | assertEquals("TestRequestBody", requestBody); 287 | return okAllIsWell(response, callback); 288 | } 289 | }); 290 | } 291 | 292 | static void registerFileUpload(final File uploadFile, final String responseText) { 293 | registerHandler("/uploadFile", HttpMode.POST, new SimpleHandler() { 294 | 295 | private static final String MULTIPART_FORMDATA_TYPE = "multipart/form-data"; 296 | 297 | private boolean isMultipartRequest(Request request) { 298 | return request.getHeaders().get(HttpHeader.CONTENT_TYPE) != null && request.getHeaders().get(HttpHeader.CONTENT_TYPE).startsWith(MULTIPART_FORMDATA_TYPE); 299 | } 300 | 301 | @Override 302 | boolean doHandle(Request request, Response response, Callback callback) { 303 | assertEquals("POST", request.getMethod()); 304 | assertTrue(isMultipartRequest(request)); 305 | 306 | String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE); 307 | MultiPartFormData.Parts parts = MultiPartFormData.getParts(request, request, contentType, new MultiPartConfig.Builder().build()); 308 | 309 | MultiPart.Part part = parts.getFirst("file-name"); 310 | assertNotNull(part); 311 | assertEquals(uploadFile.length(), part.getLength()); 312 | assertEquals(uploadFile.getName(), part.getFileName()); 313 | assertEquals(MimeType.APPLICATION_ZIP.getValue(), part.getHeaders().get(HttpHeader.CONTENT_TYPE)); 314 | 315 | return body(response, HttpStatus.CREATED_201, ContentType.TEXT_PLAIN, responseText, callback); 316 | } 317 | }); 318 | } 319 | 320 | static void registerFormData(String content, final File file1, 321 | File file2, final String responseText) { 322 | registerHandler("/formData", HttpMode.POST, new SimpleHandler() { 323 | 324 | private static final String MULTIPART_FORMDATA_TYPE = "multipart/form-data"; 325 | 326 | private boolean isMultipartRequest(Request request) { 327 | return request.getHeaders().get(HttpHeader.CONTENT_TYPE) != null 328 | && request.getHeaders().get(HttpHeader.CONTENT_TYPE).startsWith(MULTIPART_FORMDATA_TYPE); 329 | } 330 | 331 | @Override 332 | boolean doHandle(Request request, Response response, Callback callback) { 333 | assertEquals("POST", request.getMethod()); 334 | assertTrue(isMultipartRequest(request)); 335 | 336 | String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE); 337 | MultiPartFormData.Parts parts = MultiPartFormData.getParts(request, request, contentType, new MultiPartConfig.Builder().build()); 338 | 339 | MultiPart.Part file1Part = parts.getFirst("file1"); 340 | assertNotNull(file1Part); 341 | assertEquals(file1.length(), file1Part.getLength()); 342 | assertEquals(file1.getName(), file1Part.getFileName()); 343 | assertEquals(MimeType.TEXT_PLAIN.getValue(), file1Part.getHeaders().get(HttpHeader.CONTENT_TYPE)); 344 | 345 | MultiPart.Part file2Part = parts.getFirst("file2"); 346 | assertNotNull(file2Part); 347 | assertEquals(file2.length(), file2Part.getLength()); 348 | assertEquals(file2.getName(), file2Part.getFileName()); 349 | assertEquals(MimeType.APPLICATION_ZIP.getValue(), file2Part.getHeaders().get(HttpHeader.CONTENT_TYPE)); 350 | 351 | MultiPart.Part modelPart = parts.getFirst("model"); 352 | assertNotNull(modelPart); 353 | assertEquals(content, modelPart.getContentAsString(StandardCharsets.UTF_8)); 354 | assertEquals(MimeType.APPLICATION_JSON.getValue(), modelPart.getHeaders().get(HttpHeader.CONTENT_TYPE)); 355 | 356 | // So far so good 357 | return body(response, HttpStatus.CREATED_201, ContentType.TEXT_PLAIN, 358 | responseText, callback); 359 | } 360 | }); 361 | } 362 | 363 | static void registerUnwrappedPutFileUpload(final File uploadFile, final String responseText) { 364 | registerHandler("/uploadFile/" + uploadFile.getName(), HttpMode.PUT, new SimpleHandler() { 365 | 366 | private static final String MULTIPART_FORMDATA_TYPE = "multipart/form-data"; 367 | 368 | private boolean isMultipartRequest(Request request) { 369 | return request.getHeaders().get(HttpHeader.CONTENT_TYPE) != null && request.getHeaders().get(HttpHeader.CONTENT_TYPE).startsWith(MULTIPART_FORMDATA_TYPE); 370 | } 371 | 372 | @Override 373 | boolean doHandle(Request request, Response response, Callback callback) { 374 | assertEquals("PUT", request.getMethod()); 375 | assertFalse(isMultipartRequest(request)); 376 | assertEquals(uploadFile.length(), request.getLength()); 377 | assertEquals(MimeType.APPLICATION_ZIP.getValue(), request.getHeaders().get(HttpHeader.CONTENT_TYPE)); 378 | return body(response, HttpStatus.CREATED_201, ContentType.TEXT_PLAIN, responseText, callback); 379 | } 380 | }); 381 | } 382 | 383 | private static void registerHandler(String target, HttpMode method, SimpleHandler handler) { 384 | HttpRequestTestBase.registerHandler(target, method, handler); 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/main/java/jenkins/plugins/http_request/HttpRequestStep.java: -------------------------------------------------------------------------------- 1 | package jenkins.plugins.http_request; 2 | 3 | import java.io.IOException; 4 | import java.io.Serial; 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.Set; 11 | 12 | import edu.umd.cs.findbugs.annotations.NonNull; 13 | 14 | import org.apache.hc.core5.http.HttpHeaders; 15 | import org.jenkinsci.plugins.workflow.steps.Step; 16 | import org.jenkinsci.plugins.workflow.steps.StepContext; 17 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 18 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 19 | import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; 20 | import org.kohsuke.stapler.AncestorInPath; 21 | import org.kohsuke.stapler.DataBoundConstructor; 22 | import org.kohsuke.stapler.DataBoundSetter; 23 | import org.kohsuke.stapler.QueryParameter; 24 | 25 | import hudson.Extension; 26 | import hudson.FilePath; 27 | import hudson.Launcher; 28 | import hudson.model.Item; 29 | import hudson.model.Run; 30 | import hudson.model.TaskListener; 31 | import hudson.remoting.VirtualChannel; 32 | import hudson.util.FormValidation; 33 | import hudson.util.ListBoxModel; 34 | 35 | import jenkins.plugins.http_request.util.HttpRequestFormDataPart; 36 | import jenkins.plugins.http_request.util.HttpRequestNameValuePair; 37 | 38 | /** 39 | * @author Martin d'Anjou 40 | */ 41 | public final class HttpRequestStep extends Step { 42 | 43 | private final @NonNull String url; 44 | private boolean ignoreSslErrors = DescriptorImpl.ignoreSslErrors; 45 | private HttpMode httpMode = DescriptorImpl.httpMode; 46 | private String httpProxy = DescriptorImpl.httpProxy; 47 | private String proxyAuthentication = DescriptorImpl.proxyAuthentication; 48 | private String validResponseCodes = DescriptorImpl.validResponseCodes; 49 | private String validResponseContent = DescriptorImpl.validResponseContent; 50 | private MimeType acceptType = DescriptorImpl.acceptType; 51 | private MimeType contentType = DescriptorImpl.contentType; 52 | private Integer timeout = DescriptorImpl.timeout; 53 | private Boolean consoleLogResponseBody = DescriptorImpl.consoleLogResponseBody; 54 | private Boolean quiet = DescriptorImpl.quiet; 55 | private String authentication = DescriptorImpl.authentication; 56 | private String requestBody = DescriptorImpl.requestBody; 57 | private String uploadFile = DescriptorImpl.uploadFile; 58 | private String multipartName = DescriptorImpl.multipartName; 59 | private boolean wrapAsMultipart = DescriptorImpl.wrapAsMultipart; 60 | private Boolean useSystemProperties = DescriptorImpl.useSystemProperties; 61 | private boolean useNtlm = DescriptorImpl.useNtlm; 62 | private List customHeaders = DescriptorImpl.customHeaders; 63 | private List formData = DescriptorImpl.formData; 64 | private String outputFile = DescriptorImpl.outputFile; 65 | private ResponseHandle responseHandle = DescriptorImpl.responseHandle; 66 | 67 | @DataBoundConstructor 68 | public HttpRequestStep(@NonNull String url) { 69 | this.url = url; 70 | } 71 | 72 | @NonNull 73 | public String getUrl() { 74 | return url; 75 | } 76 | 77 | public boolean isIgnoreSslErrors() { 78 | return ignoreSslErrors; 79 | } 80 | 81 | @DataBoundSetter 82 | public void setIgnoreSslErrors(boolean ignoreSslErrors) { 83 | this.ignoreSslErrors = ignoreSslErrors; 84 | } 85 | 86 | @DataBoundSetter 87 | public void setHttpMode(HttpMode httpMode) { 88 | this.httpMode = httpMode; 89 | } 90 | 91 | public HttpMode getHttpMode() { 92 | return httpMode; 93 | } 94 | 95 | @DataBoundSetter 96 | public void setHttpProxy(String httpProxy) { 97 | this.httpProxy = httpProxy; 98 | } 99 | 100 | public String getHttpProxy() { 101 | return httpProxy; 102 | } 103 | 104 | @DataBoundSetter 105 | public void setValidResponseCodes(String validResponseCodes) { 106 | this.validResponseCodes = validResponseCodes; 107 | } 108 | 109 | public String getValidResponseCodes() { 110 | return validResponseCodes; 111 | } 112 | 113 | @DataBoundSetter 114 | public void setValidResponseContent(String validResponseContent) { 115 | this.validResponseContent = validResponseContent; 116 | } 117 | 118 | public String getValidResponseContent() { 119 | return validResponseContent; 120 | } 121 | 122 | @DataBoundSetter 123 | public void setAcceptType(MimeType acceptType) { 124 | this.acceptType = acceptType; 125 | } 126 | 127 | public MimeType getAcceptType() { 128 | return acceptType; 129 | } 130 | 131 | @DataBoundSetter 132 | public void setContentType(MimeType contentType) { 133 | this.contentType = contentType; 134 | } 135 | 136 | public MimeType getContentType() { 137 | return contentType; 138 | } 139 | 140 | @DataBoundSetter 141 | public void setTimeout(Integer timeout) { 142 | this.timeout = timeout; 143 | } 144 | 145 | public Integer getTimeout() { 146 | return timeout; 147 | } 148 | 149 | @DataBoundSetter 150 | public void setConsoleLogResponseBody(Boolean consoleLogResponseBody) { 151 | this.consoleLogResponseBody = consoleLogResponseBody; 152 | } 153 | 154 | public Boolean getConsoleLogResponseBody() { 155 | return consoleLogResponseBody; 156 | } 157 | 158 | @DataBoundSetter 159 | public void setQuiet(Boolean quiet) { 160 | this.quiet = quiet; 161 | } 162 | 163 | public Boolean getQuiet() { 164 | return quiet; 165 | } 166 | 167 | @DataBoundSetter 168 | public void setAuthentication(String authentication) { 169 | this.authentication = authentication; 170 | } 171 | 172 | public String getAuthentication() { 173 | return authentication; 174 | } 175 | 176 | @DataBoundSetter 177 | public void setProxyAuthentication(String proxyAuthentication) { 178 | this.proxyAuthentication = proxyAuthentication; 179 | } 180 | 181 | public String getProxyAuthentication() { 182 | return proxyAuthentication; 183 | } 184 | 185 | @DataBoundSetter 186 | public void setRequestBody(String requestBody) { 187 | this.requestBody = requestBody; 188 | } 189 | 190 | public String getRequestBody() { 191 | return requestBody; 192 | } 193 | 194 | @DataBoundSetter 195 | public void setUseSystemProperties(Boolean useSystemProperties) { 196 | this.useSystemProperties = useSystemProperties; 197 | } 198 | 199 | public Boolean getUseSystemProperties() { 200 | return useSystemProperties; 201 | } 202 | 203 | @DataBoundSetter 204 | public void setCustomHeaders(List customHeaders) { 205 | this.customHeaders = customHeaders; 206 | } 207 | 208 | public List getCustomHeaders() { 209 | return customHeaders; 210 | } 211 | 212 | public List getFormData() { 213 | return formData; 214 | } 215 | 216 | @DataBoundSetter 217 | public void setFormData(List formData) { 218 | this.formData = Collections.unmodifiableList(formData); 219 | } 220 | 221 | public String getOutputFile() { 222 | return outputFile; 223 | } 224 | 225 | @DataBoundSetter 226 | public void setOutputFile(String outputFile) { 227 | this.outputFile = outputFile; 228 | } 229 | 230 | public ResponseHandle getResponseHandle() { 231 | return responseHandle; 232 | } 233 | 234 | 235 | @DataBoundSetter 236 | public void setResponseHandle(ResponseHandle responseHandle) { 237 | this.responseHandle = responseHandle; 238 | } 239 | 240 | public String getUploadFile() { 241 | return uploadFile; 242 | } 243 | 244 | @DataBoundSetter 245 | public void setUploadFile(String uploadFile) { 246 | this.uploadFile = uploadFile; 247 | } 248 | 249 | public String getMultipartName() { 250 | return multipartName; 251 | } 252 | 253 | @DataBoundSetter 254 | public void setMultipartName(String multipartName) { 255 | this.multipartName = multipartName; 256 | } 257 | 258 | public boolean isWrapAsMultipart() { 259 | return wrapAsMultipart; 260 | } 261 | 262 | @DataBoundSetter 263 | public void setWrapAsMultipart(boolean wrapAsMultipart) { 264 | this.wrapAsMultipart = wrapAsMultipart; 265 | } 266 | 267 | @DataBoundSetter 268 | public void setUseNtlm(boolean useNtlm) { 269 | this.useNtlm = useNtlm; 270 | } 271 | 272 | public boolean isUseNtlm() { 273 | return useNtlm; 274 | } 275 | 276 | @Override 277 | public StepExecution start(StepContext context) { 278 | return new Execution(context, this); 279 | } 280 | 281 | @Override 282 | public DescriptorImpl getDescriptor() { 283 | return (DescriptorImpl) super.getDescriptor(); 284 | } 285 | 286 | List resolveHeaders() { 287 | final List headers = new ArrayList<>(); 288 | if (contentType != null && contentType != MimeType.NOT_SET) { 289 | headers.add(new HttpRequestNameValuePair(HttpHeaders.CONTENT_TYPE, contentType.getContentType().toString())); 290 | } 291 | if (acceptType != null && acceptType != MimeType.NOT_SET) { 292 | headers.add(new HttpRequestNameValuePair(HttpHeaders.ACCEPT, acceptType.getValue())); 293 | } 294 | for (HttpRequestNameValuePair header : customHeaders) { 295 | String headerName = header.getName(); 296 | String headerValue = header.getValue(); 297 | boolean maskValue = headerName.equalsIgnoreCase(HttpHeaders.AUTHORIZATION) || 298 | header.getMaskValue(); 299 | 300 | headers.add(new HttpRequestNameValuePair(headerName, headerValue, maskValue)); 301 | } 302 | return headers; 303 | } 304 | 305 | @Extension 306 | public static final class DescriptorImpl extends StepDescriptor { 307 | public static final boolean ignoreSslErrors = HttpRequest.DescriptorImpl.ignoreSslErrors; 308 | public static final HttpMode httpMode = HttpRequest.DescriptorImpl.httpMode; 309 | public static final String httpProxy = HttpRequest.DescriptorImpl.httpProxy; 310 | public static final String proxyAuthentication = HttpRequest.DescriptorImpl.proxyAuthentication; 311 | public static final String validResponseCodes = HttpRequest.DescriptorImpl.validResponseCodes; 312 | public static final String validResponseContent = HttpRequest.DescriptorImpl.validResponseContent; 313 | public static final MimeType acceptType = HttpRequest.DescriptorImpl.acceptType; 314 | public static final MimeType contentType = HttpRequest.DescriptorImpl.contentType; 315 | public static final int timeout = HttpRequest.DescriptorImpl.timeout; 316 | public static final Boolean consoleLogResponseBody = HttpRequest.DescriptorImpl.consoleLogResponseBody; 317 | public static final Boolean quiet = HttpRequest.DescriptorImpl.quiet; 318 | public static final String authentication = HttpRequest.DescriptorImpl.authentication; 319 | public static final String requestBody = HttpRequest.DescriptorImpl.requestBody; 320 | public static final String uploadFile = HttpRequest.DescriptorImpl.uploadFile; 321 | public static final String multipartName = HttpRequest.DescriptorImpl.multipartName; 322 | public static final boolean wrapAsMultipart = HttpRequest.DescriptorImpl.wrapAsMultipart; 323 | public static final Boolean useSystemProperties = HttpRequest.DescriptorImpl.useSystemProperties; 324 | public static final boolean useNtlm = HttpRequest.DescriptorImpl.useNtlm; 325 | public static final List customHeaders = Collections.emptyList(); 326 | public static final List formData = Collections.emptyList(); 327 | public static final String outputFile = ""; 328 | public static final ResponseHandle responseHandle = ResponseHandle.STRING; 329 | 330 | @Override 331 | public Set> getRequiredContext() { 332 | Set> context = new HashSet<>(); 333 | Collections.addAll(context, Run.class, TaskListener.class); 334 | return Collections.unmodifiableSet(context); 335 | } 336 | 337 | @Override 338 | public String getFunctionName() { 339 | return "httpRequest"; 340 | } 341 | 342 | @NonNull 343 | @Override 344 | public String getDisplayName() { 345 | return "Perform an HTTP Request and return a response object"; 346 | } 347 | 348 | public ListBoxModel doFillHttpModeItems() { 349 | return HttpMode.getFillItems(); 350 | } 351 | 352 | public ListBoxModel doFillAcceptTypeItems() { 353 | return MimeType.getContentTypeFillItems(); 354 | } 355 | 356 | public ListBoxModel doFillContentTypeItems() { 357 | return MimeType.getContentTypeFillItems(); 358 | } 359 | 360 | public ListBoxModel doFillResponseHandleItems() { 361 | ListBoxModel items = new ListBoxModel(); 362 | for (ResponseHandle responseHandle : ResponseHandle.values()) { 363 | items.add(responseHandle.name()); 364 | } 365 | return items; 366 | } 367 | 368 | public ListBoxModel doFillAuthenticationItems(@AncestorInPath Item project, 369 | @QueryParameter String url) { 370 | return HttpRequest.DescriptorImpl.fillAuthenticationItems(project, url); 371 | } 372 | 373 | public ListBoxModel doFillProxyAuthenticationItems(@AncestorInPath Item project, 374 | @QueryParameter String url) { 375 | return HttpRequest.DescriptorImpl.fillAuthenticationItems(project, url); 376 | } 377 | 378 | public FormValidation doCheckValidResponseCodes(@QueryParameter String value) { 379 | return HttpRequest.DescriptorImpl.checkValidResponseCodes(value); 380 | } 381 | 382 | } 383 | 384 | public static final class Execution extends SynchronousNonBlockingStepExecution { 385 | 386 | private final transient HttpRequestStep step; 387 | 388 | Execution(@NonNull StepContext context, HttpRequestStep step) { 389 | super(context); 390 | this.step = step; 391 | } 392 | 393 | @Override 394 | protected ResponseContentSupplier run() throws Exception { 395 | HttpRequestExecution exec = HttpRequestExecution.from(step, 396 | step.getQuiet() ? TaskListener.NULL : Objects.requireNonNull(getContext().get(TaskListener.class)), 397 | this); 398 | 399 | Launcher launcher = getContext().get(Launcher.class); 400 | if (launcher != null) { 401 | VirtualChannel channel = launcher.getChannel(); 402 | if (channel == null) { 403 | throw new IllegalStateException("Launcher doesn't support remoting but it is required"); 404 | } 405 | return channel.call(exec); 406 | } 407 | 408 | return exec.call(); 409 | } 410 | 411 | @Serial 412 | private static final long serialVersionUID = 1L; 413 | 414 | FilePath resolveOutputFile() { 415 | String outputFile = step.getOutputFile(); 416 | if (outputFile == null || outputFile.trim().isEmpty()) { 417 | return null; 418 | } 419 | 420 | try { 421 | FilePath workspace = getContext().get(FilePath.class); 422 | if (workspace == null) { 423 | throw new IllegalStateException("Could not find workspace to save file outputFile: " + outputFile + 424 | ". You should use it inside a 'node' block"); 425 | } 426 | return workspace.child(outputFile); 427 | } catch (IOException | InterruptedException e) { 428 | throw new IllegalStateException(e); 429 | } 430 | } 431 | 432 | FilePath resolveUploadFile() { 433 | return resolveUploadFileInternal(step.getUploadFile()); 434 | } 435 | 436 | public Item getProject() throws IOException, InterruptedException { 437 | return Objects.requireNonNull(getContext().get(Run.class)).getParent(); 438 | } 439 | 440 | private FilePath resolveUploadFileInternal(String path) { 441 | if (path == null || path.trim().isEmpty()) { 442 | return null; 443 | } 444 | 445 | try { 446 | FilePath workspace = getContext().get(FilePath.class); 447 | if (workspace == null) { 448 | throw new IllegalStateException("Could not find workspace to check existence of upload file: " + path + 449 | ". You should use it inside a 'node' block"); 450 | } 451 | FilePath uploadFilePath = workspace.child(path); 452 | if (!uploadFilePath.exists()) { 453 | throw new IllegalStateException("Could not find upload file: " + path); 454 | } 455 | return uploadFilePath; 456 | } catch (IOException | InterruptedException e) { 457 | throw new IllegalStateException(e); 458 | } 459 | } 460 | 461 | List resolveFormDataParts() { 462 | List formData = step.getFormData(); 463 | if (formData == null || formData.isEmpty()) { 464 | return Collections.emptyList(); 465 | } 466 | 467 | List resolved = new ArrayList<>(formData.size()); 468 | 469 | for (HttpRequestFormDataPart part : formData) { 470 | HttpRequestFormDataPart newPart = new HttpRequestFormDataPart(part.getUploadFile(), 471 | part.getName(), part.getFileName(), part.getContentType(), part.getBody()); 472 | newPart.setResolvedUploadFile(resolveUploadFileInternal(part.getUploadFile())); 473 | resolved.add(newPart); 474 | } 475 | 476 | return resolved; 477 | } 478 | } 479 | } 480 | --------------------------------------------------------------------------------