/hudson-core/src/main/java/hudson/console/AnnotatedLargeText.java

http://github.com/hudson/hudson · Java · 173 lines · 104 code · 17 blank · 52 comment · 4 complexity · 6fd589855693bc5cb52ab78b16d30153 MD5 · raw file

  1. /*
  2. * The MIT License
  3. *
  4. * Copyright (c) 2004-2010, Sun Microsystems, Inc.
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. package hudson.console;
  25. import com.trilead.ssh2.crypto.Base64;
  26. import hudson.model.Hudson;
  27. import hudson.remoting.ObjectInputStreamEx;
  28. import hudson.util.IOException2;
  29. import hudson.util.Secret;
  30. import hudson.util.TimeUnit2;
  31. import org.apache.commons.io.output.ByteArrayOutputStream;
  32. import org.kohsuke.stapler.Stapler;
  33. import org.kohsuke.stapler.StaplerRequest;
  34. import org.kohsuke.stapler.StaplerResponse;
  35. import org.kohsuke.stapler.framework.io.ByteBuffer;
  36. import org.kohsuke.stapler.framework.io.LargeText;
  37. import javax.crypto.Cipher;
  38. import javax.crypto.CipherInputStream;
  39. import javax.crypto.CipherOutputStream;
  40. import java.io.ByteArrayInputStream;
  41. import java.io.File;
  42. import java.io.IOException;
  43. import java.io.ObjectInputStream;
  44. import java.io.ObjectOutputStream;
  45. import java.io.OutputStream;
  46. import java.io.Writer;
  47. import java.nio.charset.Charset;
  48. import java.security.GeneralSecurityException;
  49. import java.util.zip.GZIPInputStream;
  50. import java.util.zip.GZIPOutputStream;
  51. import static java.lang.Math.abs;
  52. /**
  53. * Extension to {@link LargeText} that handles annotations by {@link ConsoleAnnotator}.
  54. *
  55. * <p>
  56. * In addition to run each line through {@link ConsoleAnnotationOutputStream} for adding markup,
  57. * this class persists {@link ConsoleAnnotator} into a byte sequence and send it to the client
  58. * as an HTTP header. The client JavaScript sends it back next time it fetches the following output.
  59. *
  60. * <p>
  61. * The serialized {@link ConsoleAnnotator} is encrypted to avoid malicious clients from instantiating
  62. * arbitrary {@link ConsoleAnnotator}s.
  63. *
  64. * @param <T>
  65. * Context type.
  66. * @author Kohsuke Kawaguchi
  67. * @since 1.349
  68. */
  69. public class AnnotatedLargeText<T> extends LargeText {
  70. /**
  71. * Can be null.
  72. */
  73. private T context;
  74. public AnnotatedLargeText(File file, Charset charset, boolean completed, T context) {
  75. super(file, charset, completed);
  76. this.context = context;
  77. }
  78. public AnnotatedLargeText(ByteBuffer memory, Charset charset, boolean completed, T context) {
  79. super(memory, charset, completed);
  80. this.context = context;
  81. }
  82. public void doProgressiveHtml(StaplerRequest req, StaplerResponse rsp) throws IOException {
  83. req.setAttribute("html",true);
  84. doProgressText(req,rsp);
  85. }
  86. /**
  87. * Aliasing what I think was a wrong name in {@link LargeText}
  88. */
  89. public void doProgressiveText(StaplerRequest req, StaplerResponse rsp) throws IOException {
  90. doProgressText(req,rsp);
  91. }
  92. /**
  93. * For reusing code between text/html and text/plain, we run them both through the same code path
  94. * and use this request attribute to differentiate.
  95. */
  96. private boolean isHtml() {
  97. return Stapler.getCurrentRequest().getAttribute("html")!=null;
  98. }
  99. @Override
  100. protected void setContentType(StaplerResponse rsp) {
  101. rsp.setContentType(isHtml() ? "text/html;charset=UTF-8" : "text/plain;charset=UTF-8");
  102. }
  103. private ConsoleAnnotator createAnnotator(StaplerRequest req) throws IOException {
  104. try {
  105. String base64 = req!=null ? req.getHeader("X-ConsoleAnnotator") : null;
  106. if (base64!=null) {
  107. Cipher sym = Secret.getCipher("AES");
  108. sym.init(Cipher.DECRYPT_MODE, Hudson.getInstance().getSecretKeyAsAES128());
  109. ObjectInputStream ois = new ObjectInputStreamEx(new GZIPInputStream(
  110. new CipherInputStream(new ByteArrayInputStream(Base64.decode(base64.toCharArray())),sym)),
  111. Hudson.getInstance().pluginManager.uberClassLoader);
  112. long timestamp = ois.readLong();
  113. if (TimeUnit2.HOURS.toMillis(1) > abs(System.currentTimeMillis()-timestamp))
  114. // don't deserialize something too old to prevent a replay attack
  115. return (ConsoleAnnotator)ois.readObject();
  116. }
  117. } catch (GeneralSecurityException e) {
  118. throw new IOException2(e);
  119. } catch (ClassNotFoundException e) {
  120. throw new IOException2(e);
  121. }
  122. // start from scratch
  123. return ConsoleAnnotator.initial(context==null ? null : context.getClass());
  124. }
  125. @Override
  126. public long writeLogTo(long start, Writer w) throws IOException {
  127. if (isHtml())
  128. return writeHtmlTo(start, w);
  129. else
  130. return super.writeLogTo(start,w);
  131. }
  132. @Override
  133. public long writeLogTo(long start, OutputStream out) throws IOException {
  134. return super.writeLogTo(start, new PlainTextConsoleOutputStream(out));
  135. }
  136. public long writeHtmlTo(long start, Writer w) throws IOException {
  137. ConsoleAnnotationOutputStream caw = new ConsoleAnnotationOutputStream(
  138. w, createAnnotator(Stapler.getCurrentRequest()), context, charset);
  139. long r = super.writeLogTo(start,caw);
  140. try {
  141. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  142. Cipher sym = Secret.getCipher("AES");
  143. sym.init(Cipher.ENCRYPT_MODE, Hudson.getInstance().getSecretKeyAsAES128());
  144. ObjectOutputStream oos = new ObjectOutputStream(new GZIPOutputStream(new CipherOutputStream(baos,sym)));
  145. oos.writeLong(System.currentTimeMillis()); // send timestamp to prevent a replay attack
  146. oos.writeObject(caw.getConsoleAnnotator());
  147. oos.close();
  148. StaplerResponse rsp = Stapler.getCurrentResponse();
  149. if (rsp!=null)
  150. rsp.setHeader("X-ConsoleAnnotator", new String(Base64.encode(baos.toByteArray())));
  151. } catch (GeneralSecurityException e) {
  152. throw new IOException2(e);
  153. }
  154. return r;
  155. }
  156. }