/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.log4j.net;

import junit.framework.TestCase;

import org.apache.log4j.AsyncAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.VectorErrorHandler;
import org.apache.log4j.HTMLLayout;

import java.util.StringTokenizer;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.Date;
import java.util.Calendar;


/**
 *    Tests for SyslogAppender
 *
 *
 * */
public class SyslogAppenderTest extends TestCase {
  /**
   * Create new instance of SyslogAppenderTest.
   * @param testName test name
   */
  public SyslogAppenderTest(final String testName) {
    super(testName);
  }

  /**
    * Resets configuration after every test.
  */
  public void tearDown() {
    LogManager.resetConfiguration();
  }

  /**
   * Test default constructor.
   */
  public void testDefaultConstructor() {
    SyslogAppender appender = new SyslogAppender();
    assertEquals("user", appender.getFacility());
    assertEquals(false, appender.getFacilityPrinting());
    assertNull(appender.getLayout());
    assertNull(appender.getSyslogHost());
    assertTrue(appender.requiresLayout());
  }

  /**
   * Test two parameter constructor.
   */
  public void testTwoParamConstructor() {
    Layout layout = new PatternLayout();
    SyslogAppender appender = new SyslogAppender(layout, 24);
    assertEquals("daemon", appender.getFacility());
    assertEquals(false, appender.getFacilityPrinting());
    assertEquals(layout, appender.getLayout());
    assertNull(appender.getSyslogHost());
    assertTrue(appender.requiresLayout());
  }

  /**
   * Test two parameter constructor with unexpected facility.
   */
  public void testTwoParamConstructorBadFacility() {
    Layout layout = new PatternLayout();
    SyslogAppender appender = new SyslogAppender(layout, 25);
    assertEquals("user", appender.getFacility());
    assertEquals(false, appender.getFacilityPrinting());
    assertEquals(layout, appender.getLayout());
    assertNull(appender.getSyslogHost());
    assertTrue(appender.requiresLayout());
  }

  /**
   * Test three parameter constructor.
   */
  public void testThreeParamConstructor() {
    Layout layout = new PatternLayout();
    SyslogAppender appender =
      new SyslogAppender(layout, "syslog.example.org", 24);
    assertEquals("daemon", appender.getFacility());
    assertEquals(false, appender.getFacilityPrinting());
    assertEquals(layout, appender.getLayout());
    assertEquals("syslog.example.org", appender.getSyslogHost());
    assertTrue(appender.requiresLayout());
  }

  /**
   * Test getFacilityString for expected facility codes.
   */
  public void testGetFacilityString() {
    String expected =
      "kern user mail daemon auth syslog lpr news "
      + "uucp cron authpriv ftp local0 local1 local2 local3 "
      + "local4 local5 local6 local7 ";
    StringBuffer actual = new StringBuffer();

    for (int i = 0; i <= 11; i++) {
      actual.append(SyslogAppender.getFacilityString(i << 3));
      actual.append(' ');
    }

    for (int i = 16; i <= 23; i++) {
      actual.append(SyslogAppender.getFacilityString(i << 3));
      actual.append(' ');
    }

    assertEquals(expected, actual.toString());
  }

  /**
   * Test getFacilityString for some unexpected facility codes.
   */
  public void testGetFacilityStringUnexpected() {
    assertNull(SyslogAppender.getFacilityString(1));
    assertNull(SyslogAppender.getFacilityString(12 << 3));
  }

  /**
   * Test getFacility with a bogus facility name.
   */
  public void testGetFacilityBogus() {
    assertEquals(-1, SyslogAppender.getFacility("bogus"));
  }

  /**
   * Test getFacility with a null facility name.
   */
  public void testGetFacilityNull() {
    assertEquals(-1, SyslogAppender.getFacility(null));
  }

  /**
   * Test getFacility for expected system facility names.
   */
  public void testGetFacilitySystemNames() {
    String[] names =
      new String[] {
        "kErn", "usEr", "MaIL", "daemOn", "auTh", "syslOg", "lPr", "newS",
        "Uucp", "croN", "authprIv", "ftP"
      };

    for (int i = 0; i <= 11; i++) {
      assertEquals(i << 3, SyslogAppender.getFacility(names[i]));
    }
  }

  /**
   * Test getFacility for expected system facility names.
   */
  public void testGetFacilityLocalNames() {
    String[] names =
      new String[] {
        "lOcal0", "LOCAL1", "loCal2", "locAl3", "locaL4", "local5", "LOCal6",
        "loCAL7"
      };

    for (int i = 0; i <= 7; i++) {
      assertEquals((16 + i) << 3, SyslogAppender.getFacility(names[i]));
    }
  }

  /**
   * Test setFacilityPrinting.
   */
  public void testSetFacilityPrinting() {
    SyslogAppender appender = new SyslogAppender();
    assertFalse(appender.getFacilityPrinting());
    appender.setFacilityPrinting(true);
    assertTrue(appender.getFacilityPrinting());
    appender.setFacilityPrinting(false);
    assertFalse(appender.getFacilityPrinting());
  }

  /**
   * Test of SyslogAppender constants.
   */
  public void testConstants() {
    assertEquals(0 << 3, SyslogAppender.LOG_KERN);
    assertEquals(1 << 3, SyslogAppender.LOG_USER);
    assertEquals(2 << 3, SyslogAppender.LOG_MAIL);
    assertEquals(3 << 3, SyslogAppender.LOG_DAEMON);
    assertEquals(4 << 3, SyslogAppender.LOG_AUTH);
    assertEquals(5 << 3, SyslogAppender.LOG_SYSLOG);
    assertEquals(6 << 3, SyslogAppender.LOG_LPR);
    assertEquals(7 << 3, SyslogAppender.LOG_NEWS);
    assertEquals(8 << 3, SyslogAppender.LOG_UUCP);
    assertEquals(9 << 3, SyslogAppender.LOG_CRON);
    assertEquals(10 << 3, SyslogAppender.LOG_AUTHPRIV);
    assertEquals(11 << 3, SyslogAppender.LOG_FTP);
    assertEquals(16 << 3, SyslogAppender.LOG_LOCAL0);
    assertEquals(17 << 3, SyslogAppender.LOG_LOCAL1);
    assertEquals(18 << 3, SyslogAppender.LOG_LOCAL2);
    assertEquals(19 << 3, SyslogAppender.LOG_LOCAL3);
    assertEquals(20 << 3, SyslogAppender.LOG_LOCAL4);
    assertEquals(21 << 3, SyslogAppender.LOG_LOCAL5);
    assertEquals(22 << 3, SyslogAppender.LOG_LOCAL6);
    assertEquals(23 << 3, SyslogAppender.LOG_LOCAL7);
  }

  /**
   * Test setFacility with null.
   * Should have no effect.
   */
  public void testSetFacilityKern() {
    SyslogAppender appender = new SyslogAppender();
    appender.setFacility("kern");
    appender.setFacility(null);
    assertEquals("kern", appender.getFacility());
  }

  /**
   * Test setFacility with null.
   * Should have no effect.
   */
  public void testSetFacilityNull() {
    SyslogAppender appender = new SyslogAppender();
    appender.setFacility("kern");
    appender.setFacility(null);
    assertEquals("kern", appender.getFacility());
  }

  /**
   * Test setFacility with bogus value.
   * Should reset to user.
   */
  public void testSetFacilityBogus() {
    SyslogAppender appender = new SyslogAppender();
    appender.setFacility("kern");
    appender.setFacility("bogus");
    assertEquals("user", appender.getFacility());
  }

  /**
   * Tests calling setFacility after appender has been activated.
   */
  public void testSetFacilityAfterActivation() {
    SyslogAppender appender = new SyslogAppender();
    appender.setName("foo");
    appender.setThreshold(Level.INFO);
    appender.setSyslogHost("localhost");
    appender.setFacility("user");
    appender.setLayout(new PatternLayout("%m%n"));

    VectorErrorHandler errorHandler = new VectorErrorHandler();
    appender.setErrorHandler(errorHandler);
    appender.activateOptions();
    appender.setFacility("kern");
    assertEquals("kern", appender.getFacility());
  }

  /**
   * Tests that append method drops messages below threshold.
   * Can't reach isSevereAsThreshold call in SyslogAppender.append
   * since it is checked in AppenderSkeleton.doAppend.
   */
  public void testAppendBelowThreshold() {
    SyslogAppender appender = new SyslogAppender();
    appender.setThreshold(Level.ERROR);
    appender.activateOptions();

    Logger logger = Logger.getRootLogger();
    logger.addAppender(appender);
    logger.info(
      "Should not be logged by SyslogAppenderTest.testAppendBelowThreshold.");
  }

  /**
   * Tests that append method drops messages below threshold.
   */
  public void testAppendNoHost() {
    SyslogAppender appender = new SyslogAppender();
    appender.setName("foo");
    appender.setThreshold(Level.INFO);

    VectorErrorHandler errorHandler = new VectorErrorHandler();
    appender.setErrorHandler(errorHandler);
    appender.setLayout(new PatternLayout("%m%n"));
    appender.activateOptions();

    Logger logger = Logger.getRootLogger();
    logger.addAppender(appender);
    logger.info(
      "Should not be logged by SyslogAppenderTest.testAppendNoHost.");
    assertEquals(1, errorHandler.size());

    //
    //  Appender is misspelled in implementation
    //
    assertEquals(
      "No syslog host is set for SyslogAppedender named \"foo\".",
      errorHandler.getMessage(0));
  }

  /**
   * Tests append method under normal conditions.
   */
  public void testAppend() {
    SyslogAppender appender = new SyslogAppender();
    appender.setName("foo");
    appender.setThreshold(Level.INFO);
    appender.setSyslogHost("localhost");
    appender.setFacility("user");
    appender.setLayout(new PatternLayout("%m%n"));

    VectorErrorHandler errorHandler = new VectorErrorHandler();
    appender.setErrorHandler(errorHandler);
    appender.activateOptions();

    //
    //  wrap SyslogAppender with an Async since appender may
    //    hang if syslogd is not accepting network messages
    //
    AsyncAppender asyncAppender = new AsyncAppender();
    asyncAppender.addAppender(appender);
    asyncAppender.activateOptions();

    Logger logger = Logger.getRootLogger();
    logger.addAppender(asyncAppender);

    Exception e =
      new Exception("Expected exception from SyslogAppenderTest.testAppend");
    logger.info(
      "Expected message from log4j unit test SyslogAppenderTest.testAppend.", e);
    assertEquals(0, errorHandler.size());
  }

  /**
    *  Tests SyslogAppender with IPv6 address.
    */
  public void testIPv6() {
      SyslogAppender appender = new SyslogAppender();
      appender.setSyslogHost("::1");
  }

  /**
    *  Tests SyslogAppender with IPv6 address enclosed in square brackets.
    */
  public void testIPv6InBrackets() {
      SyslogAppender appender = new SyslogAppender();
      appender.setSyslogHost("[::1]");
  }

  /**
    *  Tests SyslogAppender with IPv6 address enclosed in square brackets
    *     followed by port specification.
    */
  public void testIPv6AndPort() {
      SyslogAppender appender = new SyslogAppender();
      appender.setSyslogHost("[::1]:1514");
  }

  /**
    *  Tests SyslogAppender with host name enclosed in square brackets
    *     followed by port specification.
    */
  public void testHostNameAndPort() {
      SyslogAppender appender = new SyslogAppender();
      appender.setSyslogHost("localhost:1514");
  }


  /**
    *  Tests SyslogAppender with IPv4 address followed by port specification.
    */
  public void testIPv4AndPort() {
      SyslogAppender appender = new SyslogAppender();
      appender.setSyslogHost("127.0.0.1:1514");
  }

    private static String[] log(final boolean header,
                                final String msg,
                                final Exception ex,
                                final int packets) throws Exception {
        DatagramSocket ds = new DatagramSocket();
        ds.setSoTimeout(2000);

      SyslogAppender appender = new SyslogAppender();
      appender.setSyslogHost("localhost:" + ds.getLocalPort());
      appender.setName("name");
      appender.setHeader(header);
      PatternLayout pl = new PatternLayout("%m");
      appender.setLayout(pl);
      appender.activateOptions();

      Logger l = Logger.getRootLogger();
      l.addAppender(appender);
      if (ex == null) {
        l.info(msg);
      } else {
        l.error(msg, ex);
      }
      appender.close();
      String[] retval = new String[packets];
      byte[] buf = new byte[1000];
      for(int i = 0; i < packets; i++) {
          DatagramPacket p = new DatagramPacket(buf, 0, buf.length);
          ds.receive(p);
          retval[i] = new String(p.getData(), 0, p.getLength());
      }
      ds.close();
      return retval;
    }

    public void testActualLogging() throws Exception {
      String s = log(false, "greetings", null, 1)[0];
      StringTokenizer st = new StringTokenizer(s, "<>() ");
      assertEquals("14", st.nextToken());
      assertEquals("greetings", st.nextToken());
    }

    /**
     * Exception with printStackTrace that breaks earlier SyslogAppender.
     */
    private static class MishandledException extends Exception {
        /*
         *   Create new instance.
         */
        public MishandledException() {
        }

        /**
         * Print stack trace.
         * @param w print writer, may not be null.
         */
        public void printStackTrace(final java.io.PrintWriter w) {
             w.println("Mishandled stack trace follows:");
             w.println("");
             w.println("No tab here");
             w.println("\ttab here");
             w.println("\t");
        }
    }

    /**
     * Tests fix for bug 40502.
     * @throws Exception on IOException.
     */
    public void testBadTabbing() throws Exception {
        String[] s = log(false, "greetings", new MishandledException(), 6);
        StringTokenizer st = new StringTokenizer(s[0], "<>() ");
        assertEquals("11", st.nextToken());
        assertEquals("greetings", st.nextToken());
        assertEquals("<11>Mishandled stack trace follows:", s[1]);
        assertEquals("<11>", s[2]);
        assertEquals("<11>No tab here", s[3]);
        assertEquals("<11>" + SyslogAppender.TAB + "tab here", s[4]);
        assertEquals("<11>" + SyslogAppender.TAB, s[5]);
    }

    /**
     * Tests presence of timestamp if header = true.
     *
     * @throws Exception if IOException.
     */
    public void testHeaderLogging() throws Exception {
      Date preDate = new Date();
      String s = log(true, "greetings", null, 1)[0];
      Date postDate = new Date();
      assertEquals("<14>", s.substring(0, 4));

      String syslogDateStr = s.substring(4, 20);
      SimpleDateFormat fmt = new SimpleDateFormat("MMM dd HH:mm:ss ", Locale.ENGLISH);
      Date syslogDate = fmt.parse(syslogDateStr);
      Calendar cal = Calendar.getInstance(Locale.ENGLISH);
      cal.setTime(syslogDate);
      int syslogMonth = cal.get(Calendar.MONTH);
      int syslogDay = cal.get(Calendar.DATE);
      if (syslogDay < 10) {
          assertEquals(' ', syslogDateStr.charAt(4));
      }
      cal.setTime(preDate);
      int preMonth = cal.get(Calendar.MONTH);
      cal.set(Calendar.MILLISECOND, 0);
      preDate = cal.getTime();
      int syslogYear;
      if (preMonth == syslogMonth) {
          syslogYear = cal.get(Calendar.YEAR);
      } else {
          cal.setTime(postDate);
          syslogYear = cal.get(Calendar.YEAR);
      }
      cal.setTime(syslogDate);
      cal.set(Calendar.YEAR, syslogYear);
      syslogDate = cal.getTime();
      assertTrue(syslogDate.compareTo(preDate) >= 0);
      assertTrue(syslogDate.compareTo(postDate) <= 0);
    }


    /**
     * Tests that any header or footer in layout is sent.
     * @throws Exception if exception during test.
     */
    public void testLayoutHeader() throws Exception {
        DatagramSocket ds = new DatagramSocket();
        ds.setSoTimeout(2000);

      SyslogAppender appender = new SyslogAppender();
      appender.setSyslogHost("localhost:" + ds.getLocalPort());
      appender.setName("name");
      appender.setHeader(false);
      HTMLLayout pl = new HTMLLayout();
      appender.setLayout(pl);
      appender.activateOptions();

      Logger l = Logger.getRootLogger();
      l.addAppender(appender);
      l.info("Hello, World");
      appender.close();
      String[] s = new String[3];
      byte[] buf = new byte[1000];
      for(int i = 0; i < 3; i++) {
          DatagramPacket p = new DatagramPacket(buf, 0, buf.length);
          ds.receive(p);
          s[i] = new String(p.getData(), 0, p.getLength());
      }
      ds.close();
      assertEquals("<14><!DOCTYPE", s[0].substring(0,13));
      assertEquals("<14></table>", s[2].substring(0,12));
    }

    /**
     * Tests that syslog packets do not exceed 1024 bytes.
     * See bug 42087.
     * @throws Exception if exception during test.
     */
    public void testBigPackets() throws Exception {
        DatagramSocket ds = new DatagramSocket();
        ds.setSoTimeout(2000);

      SyslogAppender appender = new SyslogAppender();
      appender.setSyslogHost("localhost:" + ds.getLocalPort());
      appender.setName("name");
      appender.setHeader(false);
      PatternLayout pl = new PatternLayout("%m");
      appender.setLayout(pl);
      appender.activateOptions();

      Logger l = Logger.getRootLogger();
      l.addAppender(appender);
      StringBuffer msgbuf = new StringBuffer();
      while(msgbuf.length() < 8000) {
          msgbuf.append("0123456789");
      }
      String msg = msgbuf.toString();
      l.info(msg);
      appender.close();
      String[] s = new String[8];
      byte[] buf = new byte[1200];
      for(int i = 0; i < 8; i++) {
          DatagramPacket p = new DatagramPacket(buf, 0, buf.length);
          ds.receive(p);
          assertTrue(p.getLength() <= 1024);
          s[i] = new String(p.getData(), 0, p.getLength());
      }
      ds.close();
      StringBuffer rcvbuf = new StringBuffer(s[0]);
      rcvbuf.delete(0, 4);
      for(int i = 1; i < 8; i++) {
          rcvbuf.setLength(rcvbuf.length() - 3);
          rcvbuf.append(s[i].substring(s[i].indexOf("...") + 3));
      }
      assertEquals(msg.length(), rcvbuf.length());
      assertEquals(msg, rcvbuf.toString());
    }

}
