aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/knowcenter/wag/egov/egiz/web/servlets/SignServlet.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/at/knowcenter/wag/egov/egiz/web/servlets/SignServlet.java')
-rw-r--r--src/main/java/at/knowcenter/wag/egov/egiz/web/servlets/SignServlet.java649
1 files changed, 0 insertions, 649 deletions
diff --git a/src/main/java/at/knowcenter/wag/egov/egiz/web/servlets/SignServlet.java b/src/main/java/at/knowcenter/wag/egov/egiz/web/servlets/SignServlet.java
deleted file mode 100644
index 982e872..0000000
--- a/src/main/java/at/knowcenter/wag/egov/egiz/web/servlets/SignServlet.java
+++ /dev/null
@@ -1,649 +0,0 @@
-/**
- * <copyright> Copyright (c) 2006 by Know-Center, Graz, Austria </copyright>
- *
- * This software is the confidential and proprietary information of Know-Center,
- * Graz, Austria. You shall not disclose such Confidential Information and shall
- * use it only in accordance with the terms of the license agreement you entered
- * into with Know-Center.
- *
- * KNOW-CENTER MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
- * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
- * NON-INFRINGEMENT. KNOW-CENTER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY
- * LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
- * DERIVATIVES.
- *
- * $Id: Sign.java,v 1.7 2006/10/11 07:39:13 wprinz Exp $
- */
-package at.knowcenter.wag.egov.egiz.web.servlets;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URL;
-import java.util.Iterator;
-import java.util.List;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-import org.apache.commons.fileupload.servlet.ServletFileUpload;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import com.lowagie.text.DocumentException;
-
-import at.gv.egiz.pdfas.exceptions.ErrorCode;
-import at.gv.egiz.pdfas.exceptions.ErrorCodeHelper;
-import at.gv.egiz.pdfas.exceptions.external.ExternalErrorException;
-import at.gv.egiz.pdfas.framework.input.PdfDataSource;
-import at.gv.egiz.pdfas.impl.input.ByteArrayPdfDataSourceImpl;
-import at.gv.egiz.pdfas.itext.IText;
-import at.gv.egiz.pdfas.utils.ConfigUtils;
-import at.gv.egiz.pdfas.utils.PDFASUtils;
-import at.gv.egiz.pdfas.utils.WebUtils;
-import at.gv.egiz.pdfas.web.SignSessionInformation;
-import at.gv.egiz.pdfas.web.helper.SignServletHelper;
-import at.gv.egiz.pdfas.web.helper.TempDirHelper;
-import at.knowcenter.wag.egov.egiz.PdfAS;
-import at.knowcenter.wag.egov.egiz.cfg.SettingsReader;
-import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException;
-import at.knowcenter.wag.egov.egiz.exceptions.PlaceholderException;
-import at.knowcenter.wag.egov.egiz.exceptions.PresentableException;
-import at.knowcenter.wag.egov.egiz.pdf.TablePos;
-import at.knowcenter.wag.egov.egiz.web.ExternAppInformation;
-import at.knowcenter.wag.egov.egiz.web.FormFields;
-import at.knowcenter.wag.egov.egiz.web.SessionAttributes;
-
-/**
- * This method is the sign servlet for the pdf-as web application. It takes get
- * and post requests fill out jsp templates and give the user feedback about the
- * results of the sign process
- *
- * @author wlackner
- * @author wprinz
- */
-public class SignServlet extends HttpServlet
-{
-
- /**
- * SVUID.
- */
- private static final long serialVersionUID = -4156938216903740438L;
-
- /**
- * The log.
- */
- private static Log log = LogFactory.getLog(SignServlet.class);
- private static Log statLog = LogFactory.getLog("statistic");
-
- protected void dispatch(HttpServletRequest request, HttpServletResponse response, String resource) throws ServletException, IOException
- {
- dispatch(request, response, resource, getServletContext());
- }
-
- protected static void dispatch(HttpServletRequest request, HttpServletResponse response, String resource, ServletContext context) throws ServletException, IOException
- {
- response.setContentType("text/html");
- response.setCharacterEncoding("UTF-8");
-
- RequestDispatcher disp = context.getRequestDispatcher(resource);
- disp.forward(request, response);
- }
-
- // The sign servlet is used for processing the upload only.
- // Authentication is deactivated. if required - make an own servlet.
- // /**
- // * @author modified by tknall
- // */
- // public void doGet(HttpServletRequest request, HttpServletResponse response)
- // throws ServletException, IOException
- // {
- // String authenticate = request.getHeader(AUTH);
- // if (authenticate != null)
- // {
- // logger_.info("authenticate:" + authenticate);
- // if (authenticate.indexOf(AUTH_BASIC) == 0)
- // {
- // authenticate = authenticate.substring(AUTH_BASIC.length() + 1);
- // logger_.info("authenticate:" + authenticate);
- // authenticate = new String(CodingHelper.decodeBase64(authenticate),
- // "UTF-8");
- // logger_.info("authenticate:" + authenticate);
- //
- // String[] auth_value = authenticate.split(":");
- // String user_name = auth_value[0];
- // String user_password = auth_value[1];
- // logger_.info("username:" + user_name);
- // // start modification tknall
- // // logger_.info("password:" + user_password);
- // logger_.info("password:XXXXXXXXXXXX");
- // // stop modification tknall
- //
- // HttpSession session = request.getSession();
- // session.setAttribute(SessionAttributes.ATTRIBUTE_USER_NAME, user_name);
- // session.setAttribute(SessionAttributes.ATTRIBUTE_USER_PASSWORD,
- // user_password);
- //
- // dispatch(request, response, "/jsp/signupload.jsp");
- // return;
- // }
- // // start modification tknall
- // } else {
- // String user_name = "";
- // String user_password = "";
- // logger_.info("authenticate:User has not been authenticated!");
- // logger_.info("username: UNKNOWN");
- // logger_.info("password: XXXXXXXXXXXX");
- // HttpSession session = request.getSession();
- // session.setAttribute("uname", user_name);
- // session.setAttribute("upass", user_password);
- // dispatch(request, response, "/jsp/signupload.jsp");
- // }
-
- // request.setAttribute("error", "Falsche Authentifikation");
- // request.setAttribute("cause", "Passwort oder Benutzername ist falsch");
- // dispatch(request, response, "/jsp/error.jsp");
- // // stop modification tknall
- // }
-
- /**
- * Processes the sign upload.
- *
- * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
- * javax.servlet.http.HttpServletResponse)
- */
- public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
- {
- UploadedData ud = null;
- ExternAppInformation exappinf = null;
- TablePos pos = null;
-
- // for performance measurement
- long startTime = 0;
- if (statLog.isInfoEnabled()) {
- startTime = System.currentTimeMillis();
- }
-
-
-
- // check if pdf-as has been called by external webapp
- if (request.getParameter(FormFields.FIELD_PDF_URL) != null)
- {
-
- String preview = (String) request.getParameter(FormFields.FIELD_PREVIEW);
- String sig_type = (String) request.getParameter(FormFields.FIELD_SIGNATURE_TYPE);
- String sig_app = (String) request.getParameter(FormFields.FIELD_CONNECTOR);
- String sig_mode = (String) request.getParameter(FormFields.FIELD_MODE);
- String filename = (String) request.getParameter(FormFields.FIELD_FILENAME);
- String pdf_url = (String) request.getParameter(FormFields.FIELD_PDF_URL);
- String pdf_id = (String) request.getParameter(FormFields.FIELD_PDF_ID);
- String pdf_length = (String) request.getParameter(FormFields.FIELD_FILE_LENGTH);
- String invoke_url = (String) request.getParameter(FormFields.FIELD_INVOKE_APP_URL);
- String invoke_error_url = (String) request.getParameter(FormFields.FIELD_INVOKE_APP_ERROR_URL);
- String session_id = (String) request.getParameter(FormFields.FIELD_SESSION_ID);
- String sig_pos_y = (String) request.getParameter(FormFields.FIELD_SIGPOS_Y);
- String sig_pos_p = (String) request.getParameter(FormFields.FIELD_SIGPOS_P);
-
- // added by tknall
- if (sig_pos_y != null && sig_pos_p != null) {
- try
- {
- pos = new TablePos("y:" + sig_pos_y + ";p:" + sig_pos_p);
- }
- catch (PDFDocumentException e)
- {
- log.warn("Unable to create signature position object: " + e.getMessage(), e);
- }
- } else {
- log.debug("No signature position provided.");
- }
-
- // fixed by tknall: if we already have parameters "&" must be used instead of "?"
- String paramSeparator = (pdf_url.indexOf("?") != -1) ? "&" : "?";
- String query = pdf_url + paramSeparator + FormFields.FIELD_PDF_ID + "=" + pdf_id;
-
- // wprinz: rem: this allocation is useless
- // byte[] extern_pdf = new byte[Integer.parseInt(pdf_length)];
- URL source_url = new URL(query);
- InputStream is = source_url.openStream();
-
- // extern_pdf = toByteArray(is);
-
- // set UploadedData object...
- UploadedData ud_extern = new UploadedData();
-
- ud_extern.file_name = filename;
- ud_extern.pdfDataSource = TempDirHelper.placePdfIntoTempDir(is, filename);
- // ud_extern.pdf = extern_pdf;
-// ud_extern.preview = preview.equalsIgnoreCase("true") ? true : false;
- ud_extern.preview = "true".equalsIgnoreCase(preview);
- ud_extern.sig_app = sig_app;
- ud_extern.sig_mode = sig_mode;
- ud_extern.sig_type = sig_type;
-
- ud = ud_extern;
-
- exappinf = new ExternAppInformation(invoke_url, pdf_id, session_id, invoke_error_url);
- }
- else
- {
-
- try
- {
-
- // tzefferer: modified
- // UploadedData ud = retrieveUploadedDataFromRequest(request);
- UploadedData ud_form = retrieveUploadedDataFromRequest(request);
- ud = ud_form;
- // end modify
-
- }
- catch (Exception e)
- {
- log.error(e);
- request.setAttribute("error", "signservlet.error");
- request.setAttribute("cause", "signservlet.cause");
- request.setAttribute("resourcebundle", Boolean.TRUE);
- dispatch(request, response, "/jsp/error.jsp");
- return;
- }
- }
- try
- {
-
- ud.pdfDataSource = PdfAS.applyStrictMode(ud.pdfDataSource);
-
- SignSessionInformation si = new SignSessionInformation(); // SessionTable.generateSessionInformationObject();
- si.connector = ud.sig_app;
- si.application = "sign";
- si.mode = ud.sig_mode;
- si.pdfDataSource = ud.pdfDataSource;
- si.type = ud.sig_type;
- si.filename = formatFileName(ud.file_name);
- si.download_inline = ud.download_inline;
-
- // added tzefferer:
- si.exappinf = exappinf;
- si.pos = pos;
- // end add
-
- HttpSession session = request.getSession();
- log.info("Putting signature data into session " + session.getId());
- session.setAttribute(SessionAttributes.ATTRIBUTE_SESSION_INFORMATION, si);
-
- // String user_name = (String)
- // request.getSession().getAttribute(SessionAttributes.ATTRIBUTE_USER_NAME);
- // String user_password = (String)
- // request.getSession().getAttribute(SessionAttributes.ATTRIBUTE_USER_PASSWORD);
- // si.user_name = user_name;
- // si.user_password = user_password;
-
- SignServletHelper.prepareSign(si);
-
- if (ud.preview)
- {
- String submit_url = response.encodeURL(request.getContextPath() + "/SignPreview");
-// String signature_data_url = response.encodeURL(WebUtils.addJSessionID(request.getContextPath() + "/RetrieveSignatureData", request));
- String signature_data_url = response.encodeURL(WebUtils.buildRetrieveSignatureDataURL(request, response));
-
- request.setAttribute("submit_url", submit_url);
- request.setAttribute("signature_data_url", signature_data_url);
-
- dispatch(request, response, "/jsp/signpreview.jsp");
-
- return;
- }
-
- SignServletHelper.finishSign(si, request, response, getServletContext());
-
- // for performance measurement
- if (statLog.isInfoEnabled()) {
- long endTime = System.currentTimeMillis();
-// String toReport = "SIGN;" + si.mode + ";" + si.filename + ";"+ fileSize + ";" +
- String toReport = "SIGN;" + si.mode + ";" + si.connector + ";" + si.pdfDataSource.getLength() + ";" + (endTime - startTime);
-
- statLog.info(toReport);
- }
- }
- catch (PresentableException e)
- {
- log.error(e.getMessage(), e);
- prepareDispatchToErrorPage(e, request);
- dispatch(request, response, "/jsp/error.jsp");
- } catch (Exception e) {
- log.error(e.getMessage(), e);
- PresentableException pe = new PresentableException(ErrorCode.UNKNOWN_ERROR, e);
- prepareDispatchToErrorPage(pe, request);
- dispatch(request, response, "/jsp/error.jsp");
- }
- }
-
- // tzefferer:added
- public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
- {
- doPost(request, response);
- }
- // end add
-
- protected UploadedData retrieveUploadedDataFromRequest(HttpServletRequest request) throws ServletException, UnsupportedEncodingException, FileUploadException, PDFDocumentException
- {
- DiskFileItemFactory fif = new DiskFileItemFactory();
- fif.setRepository(SettingsReader.getTemporaryDirectory());
- ServletFileUpload sfu = new ServletFileUpload(fif);
-
- List items = sfu.parseRequest(request);
-
- FileItem preview_fi = null;
- FileItem sig_type_fi = null;
- FileItem sig_app_fi = null;
- FileItem mode_fi = null;
- FileItem file_upload_fi = null;
- FileItem download_fi = null;
- FileItem freeText = null;
- FileItem pdfa = null;
-
- Iterator it = items.iterator();
- HttpSession session = request.getSession();
- while (it.hasNext())
- {
- FileItem item = (FileItem) it.next();
- log.debug("item = " + item.getFieldName()); //$NON-NLS-1$
-
- if (log.isDebugEnabled())
- {
- if (item.isFormField())
- {
- String item_string = item.getString("UTF-8"); //$NON-NLS-1$
- log.debug(" form field string = " + item_string); //$NON-NLS-1$
- }
- else
- {
- log.debug(" filename = " + item.getName()); //$NON-NLS-1$
- log.debug(" filesize = " + item.getSize()); //$NON-NLS-1$
- }
- }
-
- if (item.getFieldName().equals(FormFields.FIELD_PREVIEW))
- {
- preview_fi = item;
- continue;
- }
-
- if (item.getFieldName().equals(FormFields.FIELD_SIGNATURE_TYPE))
- {
- sig_type_fi = item;
- session.setAttribute(UpdateFormServlet.UPLOADFORM_SIGNATURE_TYPE_KEY, sig_type_fi.getString("UTF-8"));
- continue;
- }
-
- if (item.getFieldName().equals(FormFields.FIELD_CONNECTOR))
- {
- sig_app_fi = item;
- session.setAttribute(UpdateFormServlet.UPLOADFORM_SIGNATURE_DEVICE_KEY, sig_app_fi.getString("UTF-8"));
- continue;
- }
-
- if (item.getFieldName().equals(FormFields.FIELD_MODE))
- {
- mode_fi = item;
- session.setAttribute(UpdateFormServlet.UPLOADFORM_SIGNATURE_MODE_KEY, mode_fi.getString("UTF-8"));
- continue;
- }
-
- if (item.getFieldName().equals(FormFields.FIELD_UPLOAD))
- {
- file_upload_fi = item;
- continue;
- }
-
- if (item.getFieldName().equals(FormFields.FIELD_DOWNLOAD))
- {
- download_fi = item;
- continue;
- }
-
- if (FormFields.FIELD_PDFA_ENABLED.equals(item.getFieldName())) {
- pdfa = item;
- session.setAttribute(UpdateFormServlet.UPLOADFORM_PDFA_KEY, pdfa.getString("UTF-8"));
- continue;
- }
-
- if (FormFields.FIELD_FREETEXT.equals(item.getFieldName())) {
- freeText = item;
- String value = freeText.getString("UTF-8");
- if (value != null) {
- session.setAttribute(UpdateFormServlet.UPLOADFORM_FREETEXT_KEY, value);
- }
- continue;
- }
-
- if (FormFields.FIELD_SOURCE.equals(item.getFieldName())) {
- session.setAttribute(UpdateFormServlet.UPLOADFORM_SOURCE_KEY, item.getString("UTF-8"));
- continue;
- }
-
- throw new ServletException("Unrecognized POST data."); //$NON-NLS-1$
-
- }
-
- if (preview_fi == null || sig_type_fi == null || sig_app_fi == null || (file_upload_fi == null && freeText== null) || download_fi == null)
- {
- throw new ServletException("Insufficient data provided in request"); //$NON-NLS-1$
- }
-
- String mode = mode_fi.getString("UTF-8"); //$NON-NLS-1$
- if (!mode.equals(FormFields.VALUE_MODE_BINARY) && !mode.equals(FormFields.VALUE_MODE_TEXTUAL) && !mode.equals(FormFields.VALUE_MODE_DETACHED))
- {
- throw new ServletException("The mode '" + mode + "' is unrecognized."); //$NON-NLS-1$ //$NON-NLS-2$
- }
-
- String preview_str = preview_fi.getString("UTF-8"); //$NON-NLS-1$
- boolean preview = false;
- if (preview_str.equals("true")) //$NON-NLS-1$
- {
- preview = true;
- }
-
- boolean download_inline = true;
- if (download_fi.getString("UTF-8").equals(FormFields.VALUE_DOWNLOAD_ATTACHMENT)) //$NON-NLS-1$
- {
- download_inline = false;
- }
-
- String sig_type = sig_type_fi.getString("UTF-8"); //$NON-NLS-1$
- String sig_app = sig_app_fi.getString("UTF-8"); //$NON-NLS-1$
-
- PdfDataSource pdfDataSource;
- String doc_file_name;
- // distinguish between file and freetext
- if (file_upload_fi != null) {
- log.debug("Processing file.");
- doc_file_name = TempDirHelper.extractFileNameSuffix(file_upload_fi.getName());
- log.debug("file content type =" + file_upload_fi.getContentType()); //$NON-NLS-1$
-
- String extension = VerifyServlet.extractExtension(doc_file_name);
- if (extension != null && !extension.equals("pdf")) //$NON-NLS-1$
- {
- throw new PDFDocumentException(201, "The provided file '" + doc_file_name + "' doesn't have the PDF extension (.pdf)."); //$NON-NLS-1$//$NON-NLS-2$
- }
-
- if (file_upload_fi.getSize() <= 0)
- {
- throw new PDFDocumentException(250, "The document is empty."); //$NON-NLS-1$
- }
-
- try
- {
- pdfDataSource = TempDirHelper.placePdfIntoTempDir(file_upload_fi.getInputStream(), doc_file_name);
- }
- catch (IOException e)
- {
- throw new PDFDocumentException(201, "Couldn't store the file in the temp dir.", e);
- }
- } else {
- log.debug("Processing free text.");
- try {
- boolean pdfaEnabled = pdfa != null && "true".equalsIgnoreCase(pdfa.getString());
- byte[] freeTextPDF = IText.createPDF(freeText.getString("UTF-8"), pdfaEnabled);
-
- pdfDataSource = new ByteArrayPdfDataSourceImpl(freeTextPDF);
- doc_file_name = IText.DEFAULT_FILENAME;
- } catch (DocumentException e) {
- throw new PDFDocumentException(201, "Unable to create PDF document.", e);
- } catch (IOException e) {
- throw new PDFDocumentException(201, "Unable to create PDF document.", e);
- }
-
- }
- // byte[] pdf = file_upload_fi.get();
-
- UploadedData ud = new UploadedData();
-
- ud.preview = preview;
- ud.download_inline = download_inline;
- ud.sig_type = sig_type;
- ud.sig_app = sig_app;
- ud.sig_mode = mode;
- ud.file_name = doc_file_name;
- ud.pdfDataSource = pdfDataSource;
-
- return ud;
- }
-
-
-
-
-
- public static void prepareDispatchToErrorPage(PresentableException pe, HttpServletRequest request)
- {
- request.setAttribute("PresentableException", pe);
-// if (pe instanceof ErrorCodeException)
-// {
- request.setAttribute("error", "Fehler " + pe.getErrorCode());
-
- String cause = ErrorCodeHelper.getMessageForErrorCode(pe.getErrorCode());
-
- if (pe instanceof ExternalErrorException)
- {
- ExternalErrorException eee = (ExternalErrorException) pe;
- cause = eee.getExternalErrorCode() + ": " + eee.getExternalErrorMessage();
- }
- request.setAttribute("cause", cause);
-
- if (pe.getErrorCode() == ErrorCode.PLACEHOLDER_EXCEPTION)
- {
- PlaceholderException phe = null;
- if (pe instanceof PlaceholderException)
- {
- phe = (PlaceholderException) pe;
- }
- else
- {
- phe = (PlaceholderException) pe.getCause();
- }
-
- request.setAttribute("cause", "Der Platzhalter des Feldes " + phe.getField() + " ist um " + phe.getMissing() + " Bytes zu kurz. " + cause);
- }
-// }
-// else
-// {
-// request.setAttribute("error", "PresentableException");
-// request.setAttribute("cause", pe.toString());
-// }
- }
-
- public void dispatchToPreview(String document_text, String connector, String mode, String signature_type, String submit_url, HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException
- {
- request.setAttribute("document_text", document_text);
- request.setAttribute("connector", connector);
- request.setAttribute("mode", mode);
- request.setAttribute("signature_type", signature_type);
- request.setAttribute("submit_url", submit_url);
-
- dispatch(request, response, "/jsp/signpreview.jsp");
- }
-
- /**
- * Formats the file name so that it is suitable for content disposition.
- *
- * @param file_name
- * The file name.
- * @return Returns the formatted file name.
- */
- public static String formatFileName(String file_name)
- {
- File file = new File(file_name);
- String file_name_only = file.getName();
- // the file_name contains \\ ==> remove them so Internet Explorer works
- // correctly.
- return file_name_only;
- }
-
-
-
-
-
- // tzefferer: added
- public static byte[] toByteArray(InputStream inputStream) throws IOException
- {
-
- if (inputStream == null)
- {
- return null;
- }
-
- ByteArrayOutputStream out = new ByteArrayOutputStream(8192);
- int n;
- byte[] buffer = new byte[2048];
- BufferedInputStream bufIn = new BufferedInputStream(inputStream);
- try
- {
- while ((n = bufIn.read(buffer)) != -1)
- {
- out.write(buffer, 0, n);
- }
- }
- finally
- {
- if (bufIn != null)
- {
- bufIn.close();
- }
- }
- return out.toByteArray();
- }
-
- // end add
-
- protected static class UploadedData
- {
- protected boolean preview = false;
-
- protected boolean download_inline = false;
-
- protected String sig_type = null;
-
- protected String sig_app = null;
-
- protected String sig_mode = null;
-
- protected String file_name = null;
-
- protected PdfDataSource pdfDataSource = null;
- // protected byte[] pdf = null;
- }
-} \ No newline at end of file