001: /*
002: * File : $Source: /usr/local/cvs/opencms/src-modules/org/opencms/editors/fckeditor/CmsFCKEditorFileBrowser.java,v $
003: * Date : $Date: 2008-02-27 12:05:30 $
004: * Version: $Revision: 1.7 $
005: *
006: * This library is part of OpenCms -
007: * the Open Source Content Management System
008: *
009: * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
010: *
011: * This library is free software; you can redistribute it and/or
012: * modify it under the terms of the GNU Lesser General Public
013: * License as published by the Free Software Foundation; either
014: * version 2.1 of the License, or (at your option) any later version.
015: *
016: * This library is distributed in the hope that it will be useful,
017: * but WITHOUT ANY WARRANTY; without even the implied warranty of
018: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: * Lesser General Public License for more details.
020: *
021: * For further information about Alkacon Software GmbH, please see the
022: * company website: http://www.alkacon.com
023: *
024: * For further information about OpenCms, please see the
025: * project website: http://www.opencms.org
026: *
027: * You should have received a copy of the GNU Lesser General Public
028: * License along with this library; if not, write to the Free Software
029: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
030: */
031:
032: package org.opencms.editors.fckeditor;
033:
034: import org.opencms.db.CmsDbSqlException;
035: import org.opencms.file.CmsFile;
036: import org.opencms.file.CmsProperty;
037: import org.opencms.file.CmsPropertyDefinition;
038: import org.opencms.file.CmsResource;
039: import org.opencms.file.CmsResourceFilter;
040: import org.opencms.file.CmsVfsResourceAlreadyExistsException;
041: import org.opencms.file.types.CmsResourceTypeFolder;
042: import org.opencms.file.types.CmsResourceTypeImage;
043: import org.opencms.flex.CmsFlexController;
044: import org.opencms.i18n.CmsEncoder;
045: import org.opencms.jsp.CmsJspActionElement;
046: import org.opencms.main.CmsException;
047: import org.opencms.main.CmsIllegalArgumentException;
048: import org.opencms.main.OpenCms;
049: import org.opencms.security.CmsPermissionViolationException;
050: import org.opencms.util.CmsRequestUtil;
051: import org.opencms.util.CmsStringUtil;
052: import org.opencms.workplace.CmsDialog;
053: import org.opencms.workplace.CmsWorkplaceSettings;
054: import org.opencms.xml.CmsXmlUtils;
055:
056: import java.util.ArrayList;
057: import java.util.Iterator;
058: import java.util.List;
059:
060: import javax.servlet.http.HttpServletRequest;
061: import javax.servlet.http.HttpServletResponse;
062: import javax.servlet.jsp.PageContext;
063:
064: import org.apache.commons.fileupload.FileItem;
065:
066: import org.dom4j.Document;
067: import org.dom4j.DocumentHelper;
068: import org.dom4j.Element;
069:
070: /**
071: * Implements the OpenCms Connector for integration of the FCKeditor file browser.<p>
072: *
073: * Supports browsing the OpenCms virtual file system (VFS), creating folders and uploading files to the VFS.<br>
074: * Details about the connector implementation of the FCKeditor file browser can be
075: * found at http://wiki.fckeditor.net/Developer%27s_Guide/Participating/Server_Side_Integration.<p>
076: *
077: * @author Andreas Zahner
078: *
079: * @version $Revision: 1.7 $
080: *
081: * @since 6.1.7
082: */
083: public class CmsFCKEditorFileBrowser extends CmsDialog {
084:
085: /** Value for the action: create folder. */
086: public static final int ACTION_CREATEFOLDER = 502;
087:
088: /** Value for the action: upload file. */
089: public static final int ACTION_FILEUPLOAD = 503;
090:
091: /** Value for the action: get folders. */
092: public static final int ACTION_GETFOLDERS = 500;
093:
094: /** Value for the action: get folders and files. */
095: public static final int ACTION_GETFOLDERS_FILES = 501;
096:
097: /** Attribute name for the command attribute. */
098: public static final String ATTR_COMMAND = "command";
099:
100: /** Attribute name for the name attribute. */
101: public static final String ATTR_NAME = "name";
102:
103: /** Attribute name for the number attribute. */
104: public static final String ATTR_NUMBER = "number";
105:
106: /** Attribute name for the path attribute. */
107: public static final String ATTR_PATH = "path";
108:
109: /** Attribute name for the resourceType attribute. */
110: public static final String ATTR_RESOURCETYPE = "resourceType";
111:
112: /** Attribute name for the size attribute. */
113: public static final String ATTR_SIZE = "size";
114:
115: /** Attribute name for the url attribute. */
116: public static final String ATTR_URL = "url";
117:
118: /** Name for the create folder command. */
119: public static final String COMMAND_CREATEFOLDER = "CreateFolder";
120:
121: /** Name for the file upload command. */
122: public static final String COMMAND_FILEUPLOAD = "FileUpload";
123:
124: /** Name for the get folders command. */
125: public static final String COMMAND_GETFOLDERS = "GetFolders";
126:
127: /** Name for the get folders and files command. */
128: public static final String COMMAND_GETFOLDERS_FILES = "GetFoldersAndFiles";
129:
130: /** Content type setting HTML for the response. */
131: public static final String CONTENTTYPE_HTML = "text/html";
132:
133: /** Content type setting XML for the response. */
134: public static final String CONTENTTYPE_XML = "text/xml";
135:
136: /** The dialog type. */
137: public static final String DIALOG_TYPE = "FCKeditor_file_browser";
138:
139: /** Error code for creating folders: folder already exists. */
140: public static final String ERROR_CREATEFOLDER_EXISTS = "101";
141:
142: /** Error code for creating folders: invalid folder name. */
143: public static final String ERROR_CREATEFOLDER_INVALIDNAME = "102";
144:
145: /** Error code for creating folders: no permissions. */
146: public static final String ERROR_CREATEFOLDER_NOPERMISSIONS = "103";
147:
148: /** Error code for creating folders: all ok. */
149: public static final String ERROR_CREATEFOLDER_OK = "0";
150:
151: /** Error code for creating folders: unknown error. */
152: public static final String ERROR_CREATEFOLDER_UNKNOWNERROR = "110";
153:
154: /** Error code for uploading files: invalid file. */
155: public static final String ERROR_UPLOAD_INVALID = "202";
156:
157: /** Error code for uploading files: all ok. */
158: public static final String ERROR_UPLOAD_OK = "0";
159:
160: /** Node name for the Connector node. */
161: public static final String NODE_CONNECTOR = "Connector";
162:
163: /** Node name for the CurrentFolder node. */
164: public static final String NODE_CURRENTFOLDER = "CurrentFolder";
165:
166: /** Node name for the Error node. */
167: public static final String NODE_ERROR = "Error";
168:
169: /** Node name for the File node. */
170: public static final String NODE_FILE = "File";
171:
172: /** Node name for the Files node. */
173: public static final String NODE_FILES = "Files";
174:
175: /** Node name for the Folder node. */
176: public static final String NODE_FOLDER = "Folder";
177:
178: /** Node name for the Folders node. */
179: public static final String NODE_FOLDERS = "Folders";
180:
181: /** Request parameter name for the command. */
182: public static final String PARAM_COMMAND = "Command";
183:
184: /** Request parameter name for the current folder. */
185: public static final String PARAM_CURRENTFOLDER = "CurrentFolder";
186:
187: /** Request parameter name for the new folder name. */
188: public static final String PARAM_NEWFOLDERNAME = "NewFolderName";
189:
190: /** Request parameter name for the server path. */
191: public static final String PARAM_SERVERPATH = "ServerPath";
192:
193: /** Request parameter name for the type. */
194: public static final String PARAM_TYPE = "Type";
195:
196: /** Name for the browser resource type "File". */
197: public static final String TYPE_FILE = "File";
198:
199: /** Name for the browser resource type "Flash". */
200: public static final String TYPE_FLASH = "Flash";
201:
202: /** Name for the browser resource type "Image". */
203: public static final String TYPE_IMAGE = "Image";
204:
205: /** Name for the browser resource type "Media". */
206: public static final String TYPE_MEDIA = "Media";
207:
208: /** The XML document that is returned in the response. */
209: private Document m_document;
210:
211: /** The list of multi part file items (if available). */
212: private List m_multiPartFileItems;
213:
214: /** The Command parameter. */
215: private String m_paramCommand;
216:
217: /** The CurrentFolder parameter. */
218: private String m_paramCurrentFolder;
219:
220: /** The NewFolderName parameter. */
221: private String m_paramNewFolderName;
222:
223: /** The ServerPath parameter. */
224: private String m_paramServerPath;
225:
226: /** The Type parameter. */
227: private String m_paramType;
228:
229: /**
230: * Public constructor with JSP action element.<p>
231: *
232: * @param jsp an initialized JSP action element
233: */
234: public CmsFCKEditorFileBrowser(CmsJspActionElement jsp) {
235:
236: super (jsp);
237: }
238:
239: /**
240: * Public constructor with JSP variables.<p>
241: *
242: * @param context the JSP page context
243: * @param req the JSP request
244: * @param res the JSP response
245: */
246: public CmsFCKEditorFileBrowser(PageContext context,
247: HttpServletRequest req, HttpServletResponse res) {
248:
249: this (new CmsJspActionElement(context, req, res));
250: }
251:
252: /**
253: * Creates the output for the file browser depending on the executed command.<p>
254: *
255: * @return the output for the file browser depending on the executed command
256: */
257: public String displayDialog() {
258:
259: switch (getAction()) {
260: case ACTION_CREATEFOLDER:
261: return createFolder();
262: case ACTION_FILEUPLOAD:
263: return uploadFile();
264: case ACTION_GETFOLDERS:
265: return getFolders(false);
266: case ACTION_GETFOLDERS_FILES:
267: default:
268: return getFolders(true);
269: }
270: }
271:
272: /**
273: * Fills all class parameter values from the data provided in the current request.<p>
274: *
275: * For this class, the parameters are filled manually from the request, because the needed parameter
276: * names for the file browser are in mixed case and not lower case.<p>
277: *
278: * @param request the current JSP request
279: */
280: public void fillParamValues(HttpServletRequest request) {
281:
282: // ensure a multipart request is parsed only once (for "forward" scenarios with reports)
283: if (null == request.getAttribute(REQUEST_ATTRIBUTE_MULTIPART)) {
284: // check if this is a multipart request
285: m_multiPartFileItems = CmsRequestUtil
286: .readMultipartFileItems(request);
287: if (m_multiPartFileItems != null) {
288: // this was indeed a multipart form request
289: CmsRequestUtil.readParameterMapFromMultiPart(getCms()
290: .getRequestContext().getEncoding(),
291: m_multiPartFileItems);
292: request.setAttribute(REQUEST_ATTRIBUTE_MULTIPART,
293: Boolean.TRUE);
294: }
295: }
296:
297: // manually fill the required request parameters in the members
298: setParamCommand(decodeParamValue(PARAM_COMMAND, request
299: .getParameter(PARAM_COMMAND)));
300: setParamCurrentFolder(decodeParamValue(PARAM_CURRENTFOLDER,
301: request.getParameter(PARAM_CURRENTFOLDER)));
302: setParamNewFolderName(decodeParamValue(PARAM_NEWFOLDERNAME,
303: request.getParameter(PARAM_NEWFOLDERNAME)));
304: setParamServerPath(decodeParamValue(PARAM_SERVERPATH, request
305: .getParameter(PARAM_SERVERPATH)));
306: setParamType(decodeParamValue(PARAM_TYPE, request
307: .getParameter(PARAM_TYPE)));
308: }
309:
310: /**
311: * Returns the Command parameter.<p>
312: *
313: * @return the Command parameter
314: */
315: public String getParamCommand() {
316:
317: return m_paramCommand;
318: }
319:
320: /**
321: * Returns the CurrentFolder parameter.<p>
322: *
323: * @return the CurrentFolder parameter
324: */
325: public String getParamCurrentFolder() {
326:
327: return m_paramCurrentFolder;
328: }
329:
330: /**
331: * Returns the NewFolderName parameter.<p>
332: *
333: * @return the NewFolderName parameter
334: */
335: public String getParamNewFolderName() {
336:
337: return m_paramNewFolderName;
338: }
339:
340: /**
341: * Returns the ServerPath parameter.<p>
342: *
343: * @return the ServerPath parameter
344: */
345: public String getParamServerPath() {
346:
347: return m_paramServerPath;
348: }
349:
350: /**
351: * Returns the Type parameter.<p>
352: *
353: * @return the Type parameter
354: */
355: public String getParamType() {
356:
357: return m_paramType;
358: }
359:
360: /**
361: * Sets the Command parameter.<p>
362: *
363: * @param paramCommand the Command parameter
364: */
365: public void setParamCommand(String paramCommand) {
366:
367: m_paramCommand = paramCommand;
368: }
369:
370: /**
371: * Sets the CurrentFolder parameter.<p>
372: *
373: * @param paramCurrentFolder the CurrentFolder parameter
374: */
375: public void setParamCurrentFolder(String paramCurrentFolder) {
376:
377: if (CmsStringUtil.isEmpty(paramCurrentFolder)) {
378: m_paramCurrentFolder = "/";
379: } else {
380: m_paramCurrentFolder = paramCurrentFolder;
381: }
382: }
383:
384: /**
385: * Sets the NewFolderName parameter.<p>
386: *
387: * @param paramNewFolderName the NewFolderName parameter
388: */
389: public void setParamNewFolderName(String paramNewFolderName) {
390:
391: m_paramNewFolderName = paramNewFolderName;
392: }
393:
394: /**
395: * Sets the ServerPath parameter.<p>
396: *
397: * @param paramServerPath the ServerPath parameter
398: */
399: public void setParamServerPath(String paramServerPath) {
400:
401: if (CmsStringUtil.isEmpty(paramServerPath)) {
402: m_paramServerPath = OpenCms.getSystemInfo()
403: .getOpenCmsContext()
404: + getParamCurrentFolder();
405: } else {
406: m_paramServerPath = OpenCms.getSystemInfo()
407: .getOpenCmsContext()
408: + paramServerPath;
409: }
410: }
411:
412: /**
413: * Sets the Type parameter.<p>
414: *
415: * @param paramType the Type parameter
416: */
417: public void setParamType(String paramType) {
418:
419: if (CmsStringUtil.isEmpty(paramType)) {
420: m_paramType = "";
421: } else {
422: m_paramType = paramType;
423: }
424: }
425:
426: /**
427: * Creates a folder in the file browser and returns the XML containing the error code.<p>
428: *
429: * @return the XML containing the error code for the folder creation
430: */
431: protected String createFolder() {
432:
433: createXMLHead();
434: Element error = getDocument().getRootElement().addElement(
435: NODE_ERROR);
436: try {
437: getCms().createResource(
438: getParamCurrentFolder() + getParamNewFolderName(),
439: CmsResourceTypeFolder.RESOURCE_TYPE_ID);
440: // no error occured, return error code 0
441: error.addAttribute(ATTR_NUMBER, ERROR_CREATEFOLDER_OK);
442: } catch (Exception e) {
443: // check cause of error to return a specific error code
444: if (e instanceof CmsVfsResourceAlreadyExistsException) {
445: // resource already exists
446: error.addAttribute(ATTR_NUMBER,
447: ERROR_CREATEFOLDER_EXISTS);
448: } else if (e instanceof CmsIllegalArgumentException) {
449: // invalid folder name
450: error.addAttribute(ATTR_NUMBER,
451: ERROR_CREATEFOLDER_INVALIDNAME);
452: } else if (e instanceof CmsPermissionViolationException) {
453: // no permissions to create the folder
454: error.addAttribute(ATTR_NUMBER,
455: ERROR_CREATEFOLDER_NOPERMISSIONS);
456: } else {
457: // unknown error
458: error.addAttribute(ATTR_NUMBER,
459: ERROR_CREATEFOLDER_UNKNOWNERROR);
460: }
461:
462: }
463:
464: try {
465: return CmsXmlUtils.marshal(getDocument(),
466: CmsEncoder.ENCODING_UTF_8);
467: } catch (CmsException e) {
468: // should never happen
469: return "";
470: }
471: }
472:
473: /**
474: * Creates the XML head that is used for every XML file browser response except the upload response.<p>
475: */
476: protected void createXMLHead() {
477:
478: // add the connector node
479: Element connector = getDocument().addElement(NODE_CONNECTOR);
480: connector.addAttribute(ATTR_COMMAND, getParamCommand());
481: connector.addAttribute(ATTR_RESOURCETYPE, getParamType());
482: Element currFolder = connector.addElement(NODE_CURRENTFOLDER);
483: currFolder.addAttribute(ATTR_PATH, getParamCurrentFolder());
484: currFolder.addAttribute(ATTR_URL, getParamServerPath());
485:
486: }
487:
488: /**
489: * Returns the XML document instance that is used to build the response XML.<p>
490: *
491: * @return the XML document instance that is used to build the response XML
492: */
493: protected Document getDocument() {
494:
495: if (m_document == null) {
496: m_document = DocumentHelper.createDocument();
497: }
498: return m_document;
499: }
500:
501: /**
502: * Returns the XML to list folders and/or files in the file browser window.<p>
503: *
504: * @param includeFiles flag to indicate if files are included
505: * @return the XML to list folders and/or files in the file browser window
506: */
507: protected String getFolders(boolean includeFiles) {
508:
509: createXMLHead();
510: Element folders = getDocument().getRootElement().addElement(
511: NODE_FOLDERS);
512: Element files = null;
513:
514: // generate resource filter
515: CmsResourceFilter filter;
516: if (includeFiles) {
517: // create filter to get folders and files
518: filter = CmsResourceFilter.DEFAULT.addRequireVisible();
519: files = getDocument().getRootElement().addElement(
520: NODE_FILES);
521: } else {
522: // create filter to get only folders
523: filter = CmsResourceFilter.DEFAULT_FOLDERS
524: .addRequireVisible();
525: }
526:
527: try {
528: List resources = getCms().readResources(
529: getParamCurrentFolder(), filter, false);
530: Iterator i = resources.iterator();
531: while (i.hasNext()) {
532: CmsResource res = (CmsResource) i.next();
533: if (res.isFolder()) {
534: // resource is a folder, create folder node
535: Element folder = folders.addElement(NODE_FOLDER);
536: String folderName = CmsResource.getName(res
537: .getRootPath());
538: folderName = CmsStringUtil.substitute(folderName,
539: "/", "");
540: folder.addAttribute(ATTR_NAME, folderName);
541: } else {
542: // resource is a file
543: boolean showFile = true;
544: // check if required file type is an image and filter found resources if set
545: if (TYPE_IMAGE.equals(getParamType())) {
546: showFile = (res.getTypeId() == CmsResourceTypeImage
547: .getStaticTypeId());
548: }
549: if ((showFile) && (files != null)) {
550: // create file node
551: Element file = files.addElement(NODE_FILE);
552: file.addAttribute(ATTR_NAME, CmsResource
553: .getName(res.getRootPath()));
554: file.addAttribute(ATTR_SIZE, ""
555: + (res.getLength() / 1024));
556: }
557: }
558:
559: }
560: return CmsXmlUtils.marshal(getDocument(),
561: CmsEncoder.ENCODING_UTF_8);
562: } catch (CmsException e) {
563: // error getting resource list, return empty String
564: return "";
565: }
566: }
567:
568: /**
569: * @see org.opencms.workplace.CmsWorkplace#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, javax.servlet.http.HttpServletRequest)
570: */
571: protected void initWorkplaceRequestValues(
572: CmsWorkplaceSettings settings, HttpServletRequest request) {
573:
574: // fill the parameter values in the get/set methods and check for multipart file items
575: fillParamValues(request);
576:
577: // set the dialog type
578: setParamDialogtype(DIALOG_TYPE);
579: // set the action for the JSP switch
580: if (COMMAND_FILEUPLOAD.equals(getParamCommand())) {
581: // upload file
582: setAction(ACTION_FILEUPLOAD);
583: } else if (COMMAND_CREATEFOLDER.equals(getParamCommand())) {
584: // create folder
585: setAction(ACTION_CREATEFOLDER);
586: } else if (COMMAND_GETFOLDERS.equals(getParamCommand())) {
587: // get folders
588: setAction(ACTION_GETFOLDERS);
589: } else {
590: // default: get files and folders
591: setAction(ACTION_GETFOLDERS_FILES);
592: }
593:
594: // get the top response
595: CmsFlexController controller = CmsFlexController
596: .getController(getJsp().getRequest());
597: HttpServletResponse res = controller.getTopResponse();
598: // set the response headers depending on the command to execute
599: CmsRequestUtil.setNoCacheHeaders(res);
600: String contentType = CONTENTTYPE_XML;
601: if (getAction() == ACTION_FILEUPLOAD) {
602: contentType = CONTENTTYPE_HTML;
603: }
604: res.setContentType(contentType);
605: }
606:
607: /**
608: * Uploads a file to the OpenCms VFS and returns the necessary JavaScript for the file browser.<p>
609: *
610: * @return the necessary JavaScript for the file browser
611: */
612: protected String uploadFile() {
613:
614: String errorCode = ERROR_UPLOAD_OK;
615: try {
616: // get the file item from the multipart request
617: Iterator i = m_multiPartFileItems.iterator();
618: FileItem fi = null;
619: while (i.hasNext()) {
620: fi = (FileItem) i.next();
621: if (fi.getName() != null) {
622: // found the file object, leave iteration
623: break;
624: } else {
625: // this is no file object, check next item
626: continue;
627: }
628: }
629:
630: if (fi != null) {
631: String fileName = fi.getName();
632: long size = fi.getSize();
633: long maxFileSizeBytes = OpenCms.getWorkplaceManager()
634: .getFileBytesMaxUploadSize(getCms());
635: // check file size
636: if ((maxFileSizeBytes > 0) && (size > maxFileSizeBytes)) {
637: // file size is larger than maximum allowed file size, throw an error
638: throw new Exception();
639: }
640: byte[] content = fi.get();
641: fi.delete();
642:
643: // single file upload
644: String newResname = CmsResource.getName(fileName
645: .replace('\\', '/'));
646: // determine Title property value to set on new resource
647: String title = newResname;
648: if (title.lastIndexOf('.') != -1) {
649: title = title.substring(0, title.lastIndexOf('.'));
650: }
651: List properties = new ArrayList(1);
652: CmsProperty titleProp = new CmsProperty();
653: titleProp.setName(CmsPropertyDefinition.PROPERTY_TITLE);
654: if (OpenCms.getWorkplaceManager()
655: .isDefaultPropertiesOnStructure()) {
656: titleProp.setStructureValue(title);
657: } else {
658: titleProp.setResourceValue(title);
659: }
660: properties.add(titleProp);
661:
662: // determine the resource type id from the given information
663: int resTypeId = OpenCms.getResourceManager()
664: .getDefaultTypeForName(newResname).getTypeId();
665:
666: // calculate absolute path of uploaded resource
667: newResname = getParamCurrentFolder() + newResname;
668:
669: if (!getCms().existsResource(newResname,
670: CmsResourceFilter.IGNORE_EXPIRATION)) {
671: try {
672: // create the resource
673: getCms().createResource(newResname, resTypeId,
674: content, properties);
675: } catch (CmsDbSqlException sqlExc) {
676: // SQL error, probably the file is too large for the database settings, delete file
677: getCms().lockResource(newResname);
678: getCms().deleteResource(newResname,
679: CmsResource.DELETE_PRESERVE_SIBLINGS);
680: throw sqlExc;
681: }
682: } else {
683: // resource exists, overwrite existing resource
684: checkLock(newResname);
685: CmsFile file = getCms().readFile(newResname,
686: CmsResourceFilter.IGNORE_EXPIRATION);
687: byte[] contents = file.getContents();
688: try {
689: getCms().replaceResource(newResname, resTypeId,
690: content, null);
691: } catch (CmsDbSqlException sqlExc) {
692: // SQL error, probably the file is too large for the database settings, restore content
693: file.setContents(contents);
694: getCms().writeFile(file);
695: throw sqlExc;
696: }
697: }
698: } else {
699: // no upload file found
700: throw new Exception();
701: }
702: } catch (Throwable e) {
703: // something went wrong, change error code
704: errorCode = ERROR_UPLOAD_INVALID;
705: }
706:
707: // create JavaScript to return to file browser
708: StringBuffer result = new StringBuffer(256);
709: result
710: .append("<html><head><script type=\"text/javascript\">\n");
711: result
712: .append("window.parent.frames[\"frmUpload\"].OnUploadCompleted(");
713: result.append(errorCode);
714: result.append(");\n");
715: result.append("</script></head></html>");
716: return result.toString();
717: }
718:
719: }
|