using System; using System.Web; using System.Xml; using System.Collections.Specialized; using System.Collections; using System.Net; using System.IO; using System.Text.RegularExpressions; using Exyus.Xml; using Exyus.Security; using Exyus.Caching; namespace Exyus.Web { [MediaTypes("text/xml")] public class XmlFileResource : HTTPResource { // public properties public string FileExtension = ".xml"; public string PostLocationUri = string.Empty; public string StorageFolder = string.Empty; public string DocumentsFolder = string.Empty; public string[] XHtmlNodes = null; public bool RedirectOnPost = false; public bool RedirectOnPut = false; // internal vars private Utility util = new Utility(); private string rex_notfound = "not found"; private string s_ext = string.Empty; Cache ch = new Cache(); public XmlFileResource() { if (this.ContentType == null || this.ContentType == string.Empty) { this.ContentType = Constants.cType_Html; } // set system extension s_ext = util.GetConfigSectionItem(Constants.cfg_exyusSettings,Constants.cfg_fileExtension, Constants.msc_sys_file_ext); } // currently deletes a single file public override void Delete() { XmlDocument xmlin = new XmlDocument(); XmlDocument xmlout = new XmlDocument(); string fullname = string.Empty; string out_text = string.Empty; string stor_folder = string.Empty; try { string XslArgs = this.Context.Server.MapPath(this.DocumentsFolder + "args.xsl"); string XslDeleteArgs = this.Context.Server.MapPath(this.DocumentsFolder + "delete_args.xsl"); // resolve storagefolder template stor_folder = util.ReplaceArgs(this.StorageFolder, ArgumentList); // since DELETE has no body, build 'stub xmldocument' xmlin.LoadXml(""); string arg_doc = string.Empty; arg_doc = (File.Exists(XslDeleteArgs) ? XslDeleteArgs : XslArgs); // transform into file to delete if (File.Exists(arg_doc)) { XslTransformer xslt = new XslTransformer(); out_text = xslt.ExecuteText(xmlin, arg_doc, ArgumentList); } // get file to delete if(out_text == string.Empty) throw new HttpException(400, "bad request"); else fullname = this.Context.Server.MapPath(string.Format("{0}{1}{2}",stor_folder,out_text,this.FileExtension)); // delete the physical file if (File.Exists(fullname)) { File.Delete(fullname); this.StatusCode = HttpStatusCode.NoContent; ch.ClearCache(this.ImmediateCacheUriTemplates, this.BackgroundCacheUriTemplates, "", ArgumentList, util.LoadUriCache()); } else throw new FileNotFoundException(string.Format(rex_notfound + " [{0}]", this.AbsoluteUri.Replace(s_ext, ""))); xmlout = null; } catch (HttpException hex) { this.StatusCode = (HttpStatusCode)hex.GetHttpCode(); this.StatusDescription = hex.Message; out_text = util.RenderError("http error", hex.Message, CurrentMediaType); } catch (FileNotFoundException fex) { this.StatusCode = HttpStatusCode.NotFound; this.StatusDescription = fex.Message; out_text = util.RenderError("file error", fex.Message, CurrentMediaType); } catch (Exception ex) { this.StatusCode = HttpStatusCode.InternalServerError; this.StatusDescription = ex.Message; out_text = util.RenderError("unknown error", ex.Message, CurrentMediaType); } if (xmlout != null) this.Response = util.FixEncoding(xmlout.OuterXml); else this.Response = null; xmlout = null; } // get a single item or a list of items in storage public override void Get() { XmlDocument xmlout = new XmlDocument(); XmlDocument xmlin = new XmlDocument(); string out_text = string.Empty; string xsl_file = string.Empty; string stor_folder = string.Empty; // possible control documents string XslArgs = this.Context.Server.MapPath(this.DocumentsFolder + "args.xsl"); string XslGetArgs = this.Context.Server.MapPath(this.DocumentsFolder + "get_args.xsl"); string xslGetRequest = this.Context.Server.MapPath(this.DocumentsFolder + "get_request.xsl"); string xslGetRequestContentType = this.Context.Server.MapPath(this.DocumentsFolder + (CurrentMediaType == string.Empty ? "get_request.xsl" : string.Format("get_request_{0}.xsl", CurrentFileType))); string xslGetResponse = this.Context.Server.MapPath(this.DocumentsFolder + "get_response.xsl"); string xslGetResponseContentType = this.Context.Server.MapPath(this.DocumentsFolder + (CurrentMediaType == string.Empty ? "get_response.xsl" : string.Format("get_response_{0}.xsl", CurrentFileType))); try { // see if we have a current copy if(ch.CachedResourceIsValid((HTTPResource)this)) return; // since GET has no body, build 'stub xmldocument' xmlin.LoadXml(""); // transform into file to get xsl_file = (File.Exists(XslGetArgs) ? XslGetArgs : XslArgs); if (File.Exists(xsl_file)) { XslTransformer xslt = new XslTransformer(); out_text = xslt.ExecuteText(xmlin, xsl_file, ArgumentList); } // fix up storage folder stor_folder = util.ReplaceArgs(this.StorageFolder, ArgumentList); // must be a single document if (out_text != string.Empty) { string fullname = this.Context.Server.MapPath(string.Format("{0}{1}{2}",stor_folder,out_text,this.FileExtension)); if (File.Exists(fullname)) { using (XmlTextReader xtr = new XmlTextReader(fullname)) { xmlout.Load(xtr); xtr.Close(); } } else throw new FileNotFoundException(string.Format(rex_notfound + " [{0}]", this.AbsoluteUri.Replace(s_ext, ""))); xsl_file = (File.Exists(xslGetResponseContentType) ? xslGetResponseContentType : xslGetResponse); util.SafeAdd(ref ArgumentList, "_mode", "item"); if (File.Exists(xsl_file)) { XslTransformer xslt = new XslTransformer(); out_text = xslt.ExecuteText(xmlout, xsl_file, ArgumentList); } } else { // create list of files xmlin = new XmlDocument(); out_text = ""; string path = this.Context.Server.MapPath(stor_folder); if (Directory.Exists(path)) { foreach (string file in System.IO.Directory.GetFiles(path, "*"+this.FileExtension)) { FileInfo fi = new FileInfo(file); out_text += string.Format("{1}", fi.FullName, fi.Name.Replace(fi.Extension,"")); } out_text += ""; xmlin.LoadXml(out_text); // transform into list util.SafeAdd(ref ArgumentList, "_mode", "list"); xsl_file = (File.Exists(xslGetResponseContentType) ? xslGetResponseContentType : xslGetResponse); if (File.Exists(xsl_file)) { XslTransformer xslt = new XslTransformer(); out_text = xslt.ExecuteText(xmlin, xsl_file, ArgumentList); } else out_text = xmlin.OuterXml; } else throw new FileNotFoundException(string.Format(rex_notfound + " [{0}]", this.Context.Request.RawUrl.Replace(s_ext, ""))); } ch.CacheResource((HTTPResource)this, util.FixEncoding(out_text)); } catch (HttpException hex) { this.StatusCode = (HttpStatusCode)hex.GetHttpCode(); this.StatusDescription = hex.Message; out_text = util.RenderError("http error", hex.Message, CurrentMediaType); } catch (FileNotFoundException fex) { this.StatusCode = HttpStatusCode.NotFound; this.StatusDescription = fex.Message; out_text = util.RenderError("file error", fex.Message, CurrentMediaType); } catch (Exception ex) { this.StatusCode = HttpStatusCode.InternalServerError; this.StatusDescription = ex.Message; out_text = util.RenderError("unknown error", ex.Message, CurrentMediaType); } // return the results this.Response = util.FixEncoding(out_text); xmlin = null; xmlout = null; } public override void Head() { this.Get(); this.Response = null; } public override void Post() { XmlDocument xmlout = new XmlDocument(); XmlDocument xmlin = new XmlDocument(); XmlDocument xmlargs = new XmlDocument(); string stor_folder = string.Empty; string id = string.Empty; string xsl_file = string.Empty; string xsd_file = string.Empty; string original_contentType = this.ContentType; string out_text = string.Empty; string XslArgs = this.Context.Server.MapPath(this.DocumentsFolder + "args.xsl"); string XslPostArgs = this.Context.Server.MapPath(this.DocumentsFolder + "post_args.xsl"); string XsdFile = this.Context.Server.MapPath(this.DocumentsFolder + "post.xsd"); string XsdFileMtype = this.Context.Server.MapPath(this.DocumentsFolder + (CurrentMediaType == string.Empty ? "post.xsd" : string.Format("post_{0}.xsd", CurrentFileType))); string XslPostRequest = this.Context.Server.MapPath(this.DocumentsFolder + "post_request.xsl"); string XslPostRequestMtype = this.Context.Server.MapPath(this.DocumentsFolder + (CurrentMediaType == string.Empty ? "post_request.xsl" : string.Format("post_request_{0}.xsl", CurrentFileType))); try { // validate args xsl_file = (File.Exists(XslPostArgs) ? XslPostArgs : XslArgs); if (File.Exists(xsl_file)) { xmlargs.LoadXml(""); XslTransformer xslt = new XslTransformer(); id = xslt.ExecuteText(xmlargs, xsl_file, ArgumentList); } // transform *must not* return doc id! if (id != string.Empty) throw new HttpException(400, "Cannot POST using resource id"); // fix up the storage folder stor_folder = util.ReplaceArgs(this.StorageFolder, ArgumentList); // get the xmldoc from the entity this.Context.Request.InputStream.Position = 0; switch (CurrentMediaType.ToLower()) { case Constants.cType_FormUrlEncoded: xmlin = util.ProcessFormVars(this.Context.Request.Form); break; case Constants.cType_Json: xmlin = util.ProcessJSON(this.Context.Request.InputStream); break; default: xmlin.Load(this.Context.Request.InputStream); break; } // validate the doc xsd_file = (File.Exists(XsdFileMtype) ? XsdFileMtype : XsdFile); if (File.Exists(xsd_file)) { SchemaValidator sv = new SchemaValidator(); string sch_error = sv.Execute(xmlin, xsd_file); if (sch_error != string.Empty) throw new HttpException(422, sch_error); } // validate html util.ValidateXHtmlNodes(ref xmlin,this.XHtmlNodes); // transform xmldoc into final form (if needed) xsl_file = (File.Exists(XslPostRequestMtype)?XslPostRequestMtype:XslPostRequest); if (File.Exists(xsl_file)) { XslTransformer xslt = new XslTransformer(); out_text = xslt.ExecuteText(xmlin, xsl_file, ArgumentList); xmlout.LoadXml(out_text); } else xmlout = xmlin; // save it by creating a unique key id = util.UID(); string fullname = this.Context.Server.MapPath(string.Format("{0}{1}{2}",stor_folder,id,this.FileExtension)); if (File.Exists(fullname)) throw new HttpException(400, "already exists"); else { if (!Directory.Exists(this.Context.Server.MapPath(stor_folder))) Directory.CreateDirectory(this.Context.Server.MapPath(stor_folder)); xmlout.Save(fullname); } // redirect to created item this.StatusCode = (this.RedirectOnPost?HttpStatusCode.Redirect:HttpStatusCode.Created); this.Location = util.GetConfigSectionItem(Constants.cfg_exyusSettings, Constants.cfg_rootfolder) + util.ReplaceArgs(this.PostLocationUri.Replace("{id}", id), ArgumentList); xmlout = null; // cache invalidation ch.ClearCache(this.ImmediateCacheUriTemplates, this.BackgroundCacheUriTemplates, "", ArgumentList, util.LoadUriCache()); } catch (HttpException hex) { this.StatusCode = (HttpStatusCode)hex.GetHttpCode(); this.StatusDescription = hex.Message; out_text = util.RenderError("http error", hex.Message, CurrentMediaType); } catch (Exception ex) { this.StatusCode = HttpStatusCode.InternalServerError; this.StatusDescription = ex.Message; out_text = util.RenderError("unknown error", ex.Message, CurrentMediaType); } if (xmlout != null) this.Response = util.FixEncoding(xmlout.OuterXml); else { if (out_text != string.Empty) this.Response = out_text; else this.Response = null; } // if we were using form-posting, reset to preferred content type (text/html, most likely) if (this.ContentType == Constants.cType_FormUrlEncoded) { this.ContentType = (original_contentType == Constants.cType_FormUrlEncoded ? Constants.cType_Html : original_contentType); } xmlin = null; xmlout = null; } // overwrites existing item public override void Put() { XmlDocument xmlout = new XmlDocument(); XmlDocument xmlin = new XmlDocument(); XmlDocument xmlargs = new XmlDocument(); string out_text = string.Empty; string id = string.Empty; string stor_folder = string.Empty; string xsl_file = string.Empty; string original_contentType = this.ContentType; string XslArgs = this.Context.Server.MapPath(this.DocumentsFolder + "args.xsl"); string XslPutArgs = this.Context.Server.MapPath(this.DocumentsFolder + "put_args.xsl"); string XsdFile = this.Context.Server.MapPath(this.DocumentsFolder + "put.xsd"); string XslPutRequest = this.Context.Server.MapPath(this.DocumentsFolder + "put_request.xsl"); string XslPutRequestMtype = this.Context.Server.MapPath(this.DocumentsFolder + (CurrentMediaType == string.Empty ? "put_request.xsl" : string.Format("put_request_{0}.xsl", CurrentFileType))); try { stor_folder = util.ReplaceArgs(this.StorageFolder, ArgumentList); // validate args xsl_file = (File.Exists(XslPutArgs) ? XslPutArgs : XslArgs); if (File.Exists(xsl_file)) { xmlargs.LoadXml(""); XslTransformer xslt = new XslTransformer(); id = xslt.ExecuteText(xmlargs, xsl_file, ArgumentList); } // transform *must* return doc id! if (id == string.Empty) throw new HttpException(400, "missing resource id"); // get the xmldoc from the entity body xmlin = new XmlDocument(); this.Context.Request.InputStream.Position=0; switch (CurrentMediaType.ToLower()) { case Constants.cType_FormUrlEncoded: xmlin = util.ProcessFormVars(this.Context.Request.Form); break; case Constants.cType_Json: xmlin = util.ProcessJSON(this.Context.Request.InputStream); break; default: xmlin.Load(this.Context.Request.InputStream); break; } // validate the doc if (File.Exists(XsdFile)) { SchemaValidator sv = new SchemaValidator(); string sch_error = sv.Execute(xmlin, XsdFile); if (sch_error != string.Empty) throw new HttpException(422, sch_error); } // validate html nodes util.ValidateXHtmlNodes(ref xmlin, this.XHtmlNodes); // transform xmldoc if needed xsl_file = (File.Exists(XslPutRequestMtype) ? XslPutRequestMtype : XslPutRequest); if (File.Exists(xsl_file)) { XslTransformer xslt = new XslTransformer(); out_text = xslt.ExecuteText(xmlin, xsl_file, ArgumentList); xmlout.LoadXml(out_text); } else xmlout = xmlin; // get ready to save it string fullname = this.Context.Server.MapPath(string.Format("{0}{1}{2}", stor_folder, id, this.FileExtension)); bool file_exists = File.Exists(fullname); // set to only allow updates? (quick check) if (this.AllowCreateOnPut == false && !file_exists) throw new FileNotFoundException(string.Format(rex_notfound + " [{0}]", this.Context.Request.RawUrl.Replace(s_ext, ""))); // ok, we allow update and create bool save_item = false; string etag = string.Empty; string last_mod = string.Empty; string put_error = "Unable to complete PUT."; // generic message // next, do a head request for this resource HTTPClient cl = new HTTPClient(); ExyusPrincipal ep = (ExyusPrincipal)this.Context.User; cl.Credentials = new NetworkCredential(((ExyusIdentity)ep.Identity).Name, ((ExyusIdentity)ep.Identity).Password); // load headers for request PutHeaders ph = new PutHeaders(this.Context); if (ph.IfMatch != string.Empty) cl.RequestHeaders.Set(Constants.hdr_if_none_match, ph.IfMatch); if (ph.IfUnmodifiedSince != string.Empty) cl.RequestHeaders.Set(Constants.hdr_if_modified_since, ph.IfUnmodifiedSince); if(ph.IfUnmodifiedSince==string.Empty && ph.LastModified!=string.Empty) cl.RequestHeaders.Set(Constants.hdr_if_modified_since, ph.LastModified); // make request for existing resource try { out_text = cl.Execute( string.Format("{0}://{1}{2}", this.Context.Request.Url.Scheme, this.Context.Request.Url.DnsSafeHost, this.Context.Request.RawUrl), "head", (CurrentMediaType == Constants.cType_FormUrlEncoded ? "text/html" : CurrentMediaType)); // record exists, this must be an update etag = util.GetHttpHeader(Constants.hdr_etag, (NameValueCollection)cl.ResponseHeaders); last_mod = util.GetHttpHeader(Constants.hdr_last_modified, (NameValueCollection)cl.ResponseHeaders); // sort out update conditions util.CheckPutUpdateCondition(ph, etag, last_mod, ref put_error, ref save_item); } catch (HttpException hex2) { // record not there or some other error int code = hex2.GetHttpCode(); switch (code) { // record exists w/o changes, we can update! case (int)HttpStatusCode.NotModified: save_item = true; break; // see if it's ok to save case (int)HttpStatusCode.NotFound: // sort out create conditions util.CheckPutCreateCondition(ph, this.AllowCreateOnPut, etag, ref put_error, ref save_item); break; // some other error, omgz! default: put_error = hex2.Message + " Unable to PUT."; save_item = false; break; } } // ok, all passed - try to save it if (save_item==true) { // save it (include the folder, if needed) if (!Directory.Exists(this.Context.Server.MapPath(stor_folder))) Directory.CreateDirectory(this.Context.Server.MapPath(stor_folder)); xmlout.Save(fullname); this.StatusCode = (this.RedirectOnPut?HttpStatusCode.Redirect:HttpStatusCode.OK); this.Location = this.AbsoluteUri; xmlout = null; // cache invalidation ch.ClearCache(this.ImmediateCacheUriTemplates, this.BackgroundCacheUriTemplates, "", ArgumentList, util.LoadUriCache()); } else throw new HttpException((int)HttpStatusCode.PreconditionFailed, put_error); } catch (HttpException hex) { this.StatusCode = (HttpStatusCode)hex.GetHttpCode(); this.StatusDescription = hex.Message; out_text = util.RenderError("http error", hex.Message, CurrentMediaType); } catch (FileNotFoundException fex) { this.StatusCode = HttpStatusCode.NotFound; this.StatusDescription = fex.Message; out_text = util.RenderError("file error", fex.Message, CurrentMediaType); } catch (Exception ex) { this.StatusCode = HttpStatusCode.InternalServerError; this.StatusDescription = ex.Message; out_text = util.RenderError("unknown error", ex.Message, CurrentMediaType); } if (xmlout != null) this.Response = util.FixEncoding(xmlout.OuterXml); else { if (out_text != string.Empty) this.Response = out_text; else this.Response = null; } // if we were using form-posting, reset to preferred content type (text/html, most likely) if (this.ContentType == Constants.cType_FormUrlEncoded) { this.ContentType = (original_contentType == Constants.cType_FormUrlEncoded ? Constants.cType_Html : original_contentType); } xmlin = null; xmlout = null; } } }