diff --git a/Builder/IISMainHandler/build.txt b/Builder/IISMainHandler/build.txt index a9d8b73..fefb598 100644 --- a/Builder/IISMainHandler/build.txt +++ b/Builder/IISMainHandler/build.txt @@ -1 +1 @@ -257 \ No newline at end of file +284 \ No newline at end of file diff --git a/Builder/IISUploadHandler/build.txt b/Builder/IISUploadHandler/build.txt new file mode 100644 index 0000000..19c7bdb --- /dev/null +++ b/Builder/IISUploadHandler/build.txt @@ -0,0 +1 @@ +16 \ No newline at end of file diff --git a/Builder/IISUploadHandler/postbuild.bat b/Builder/IISUploadHandler/postbuild.bat new file mode 100644 index 0000000..9c9b7a9 --- /dev/null +++ b/Builder/IISUploadHandler/postbuild.bat @@ -0,0 +1 @@ +@echo off diff --git a/Builder/IISUploadHandler/prebuild.bat b/Builder/IISUploadHandler/prebuild.bat new file mode 100644 index 0000000..9c9b7a9 --- /dev/null +++ b/Builder/IISUploadHandler/prebuild.bat @@ -0,0 +1 @@ +@echo off diff --git a/Builder/IISUploadHandler/product.wxs b/Builder/IISUploadHandler/product.wxs new file mode 100644 index 0000000..e9743dc --- /dev/null +++ b/Builder/IISUploadHandler/product.wxs @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Common/Common.csproj b/Common/Common.csproj index af70483..b2c3365 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -64,11 +64,13 @@ + + diff --git a/Common/Config.cs b/Common/Config.cs index db4cd92..6b3c45b 100644 --- a/Common/Config.cs +++ b/Common/Config.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections.Specialized; +using FLocal.Core; namespace FLocal.Common { - public class Config : FLocal.Core.Config { + public class Config : Config { public readonly string InitTime; @@ -20,6 +21,10 @@ namespace FLocal.Common { public readonly string SaltPasswords; + public readonly string SaltUploader; + + public readonly string UploaderUrl; + protected Config(NameValueCollection data) : base(data) { this.InitTime = DateTime.Now.ToLongTimeString(); this.mainConnection = new MySQLConnector.Connection(data["ConnectionString"], MySQLConnector.PostgresDBTraits.instance); @@ -27,6 +32,8 @@ namespace FLocal.Common { this.DirSeparator = System.IO.Path.DirectorySeparatorChar.ToString(); this.SaltMigration = data["SaltMigration"]; this.SaltPasswords = data["SaltPasswords"]; + this.SaltUploader = data["SaltUploader"]; + this.UploaderUrl = data["UploaderUrl"]; } public static void Init(NameValueCollection data) { @@ -44,8 +51,17 @@ namespace FLocal.Common { public static void Transactional(Action action) { using(Core.DB.Transaction transaction = Core.DB.IDBConnectionExtensions.beginTransaction(instance.mainConnection)) { - action(transaction); - transaction.Commit(); + bool success = false; + try { + action(transaction); + success = true; + transaction.Commit(); + } catch(FLocalException) { + if(!success) { + transaction.Rollback(); + } + throw; + } } } diff --git a/Common/UploadManager.cs b/Common/UploadManager.cs new file mode 100644 index 0000000..72d1d26 --- /dev/null +++ b/Common/UploadManager.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using FLocal.Core; +using FLocal.Common.dataobjects; +using FLocal.Core.DB; +using FLocal.Core.DB.conditions; +using System.Net; + +namespace FLocal.Common { + public static class UploadManager { + + private class _AlreadyUploadedException : FLocalException { + + public _AlreadyUploadedException() : base("Already uploaded") { + } + + } + + public class AlreadyUploadedException : FLocalException { + + public readonly int uploadId; + + public AlreadyUploadedException(int uploadId) : base("Already uploaded " + uploadId) { + this.uploadId = uploadId; + } + + } + + private const int MAX_UPLOAD_FILESIZE = 1024*1024; + + private static HashSet allowedExtensions = new HashSet() { + "jpg", + "gif", + "png", + }; + + public static void UploadFile(Stream fileStream, string _extension, DateTime uploadDate, User uploader, int? id) { + + string extension = _extension.ToLower(); + + if(!allowedExtensions.Contains(extension)) throw new FLocalException("Unsupported extension"); + if(fileStream.Length > MAX_UPLOAD_FILESIZE) throw new FLocalException("File is too big"); + + byte[] data = new byte[fileStream.Length]; + if(fileStream.Read(data, 0, (int)fileStream.Length) != fileStream.Length) { + throw new FLocalException("File is incomplete"); + } + + string file_md5 = Util.md5(fileStream); + AbstractCondition condition = new ComparisonCondition( + Upload.TableSpec.instance.getColumnSpec(Upload.TableSpec.FIELD_HASH), + ComparisonType.EQUAL, + file_md5 + ); + try { + if(Config.instance.mainConnection.GetCountByConditions(Upload.TableSpec.instance, condition, new JoinSpec[0]) > 0) { + throw new _AlreadyUploadedException(); + } + Config.Transactional(transaction => { + Config.instance.mainConnection.lockTable(transaction, Upload.TableSpec.instance); + /*if(Config.instance.mainConnection.GetCountByConditions(Upload.TableSpec.instance, condition, new JoinSpec[0]) > 0) { + throw new _AlreadyUploadedException(); + }*/ + //TODO: ??? + + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Config.instance.UploaderUrl + "Upload/?extension=" + extension + "&signature=" + Util.md5(file_md5 + " " + Config.instance.SaltUploader)); + request.Method = "POST"; + request.ContentLength = data.Length; + Stream requestStream = request.GetRequestStream(); + requestStream.Write(data, 0, data.Length); + requestStream.Close(); + + HttpWebResponse response; + try { + response = (HttpWebResponse)request.GetResponse(); + } catch(WebException e) { + response = (HttpWebResponse)e.Response; + } + using(StreamReader reader = new StreamReader(response.GetResponseStream())) { + string result = reader.ReadToEnd(); + if(result != "OK") { + throw new CriticalException("Cannot upload file to upload service: " + result); + } + } + + Dictionary row = new Dictionary(); + if(id.HasValue) row[Upload.TableSpec.FIELD_ID] = id.ToString(); + row[Upload.TableSpec.FIELD_HASH] = file_md5; + row[Upload.TableSpec.FIELD_EXTENSION] = extension; + row[Upload.TableSpec.FIELD_UPLOADDATE] = uploadDate.ToUTCString(); + row[Upload.TableSpec.FIELD_USERID] = uploader.id.ToString(); + row[Upload.TableSpec.FIELD_SIZE] = data.Length.ToString(); + Config.instance.mainConnection.insert(transaction, Upload.TableSpec.instance, row); + }); + } catch(_AlreadyUploadedException) { + throw new AlreadyUploadedException( + int.Parse( + Config.instance.mainConnection.LoadIdsByConditions( + Upload.TableSpec.instance, + condition, + Diapasone.unlimited, + new JoinSpec[0] + )[0] + ) + ); + } + } + + } +} diff --git a/Common/dataobjects/Upload.cs b/Common/dataobjects/Upload.cs new file mode 100644 index 0000000..9093fa5 --- /dev/null +++ b/Common/dataobjects/Upload.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using FLocal.Core; +using FLocal.Core.DB; +using FLocal.Core.DB.conditions; + +namespace FLocal.Common.dataobjects { + public class Upload : SqlObject { + + public class TableSpec : ISqlObjectTableSpec { + public const string TABLE = "Uploads"; + public const string FIELD_ID = "Id"; + public const string FIELD_HASH = "MD5"; + public const string FIELD_EXTENSION = "Extension"; + public const string FIELD_SIZE = "Size"; + public const string FIELD_UPLOADDATE = "UploadDate"; + public const string FIELD_USERID = "UserId"; + public static readonly TableSpec instance = new TableSpec(); + public string name { get { return TABLE; } } + public string idName { get { return FIELD_ID; } } + public void refreshSqlObject(int id) { Refresh(id); } + } + + protected override ISqlObjectTableSpec table { get { return TableSpec.instance; } } + + private string _hash; + public string hash { + get { + this.LoadIfNotLoaded(); + return this._hash; + } + } + + private string _extension; + public string extension { + get { + this.LoadIfNotLoaded(); + return this._extension; + } + } + + private int _size; + public int size { + get { + this.LoadIfNotLoaded(); + return this._size; + } + } + + private DateTime _uploadDate; + public DateTime uploadDate { + get { + this.LoadIfNotLoaded(); + return this._uploadDate; + } + } + + private int? _userId; + public int? userId { + get { + this.LoadIfNotLoaded(); + return this._userId; + } + } + public User user { + get { + return User.LoadById(this.userId.Value); + } + } + + protected override void doFromHash(Dictionary data) { + this._hash = data[TableSpec.FIELD_HASH]; + this._extension = data[TableSpec.FIELD_EXTENSION]; + this._size = int.Parse(data[TableSpec.FIELD_SIZE]); + this._uploadDate = Util.ParseDateTimeFromTimestamp(data[TableSpec.FIELD_UPLOADDATE]).Value; + this._userId = Util.ParseInt(data[TableSpec.FIELD_USERID]); + } + + } +} diff --git a/Core/Util.cs b/Core/Util.cs index cf69d3c..17e27e5 100644 --- a/Core/Util.cs +++ b/Core/Util.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.IO; namespace FLocal.Core { @@ -165,6 +166,34 @@ namespace FLocal.Core { return sBuilder.ToString(); } + /// + /// Code sample from http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5.aspx + /// + /// + /// + public static string md5(Stream inputStream) { + inputStream.Position = 0; + + System.Security.Cryptography.HashAlgorithm md5Hasher = System.Security.Cryptography.MD5.Create(); + + // Convert the input string to a byte array and compute the hash. + byte[] data = md5Hasher.ComputeHash(inputStream); + + // Create a new Stringbuilder to collect the bytes + // and create a string. + StringBuilder sBuilder = new StringBuilder(); + + // Loop through each byte of the hashed data + // and format each one as a hexadecimal string. + for (int i = 0; i < data.Length; i++) + { + sBuilder.Append(data[i].ToString("x2")); + } + + // Return the hexadecimal string. + return sBuilder.ToString(); + } + public static bool throws(Action action) where TException : Exception { try { action(); @@ -174,6 +203,24 @@ namespace FLocal.Core { } } + private static Dictionary extension2mime = new Dictionary(); + public static string getMimeByExtension(string extension) { + if(!extension.StartsWith(".")) extension = "." + extension; + if(!extension2mime.ContainsKey(extension)) { + lock(extension2mime) { + if(!extension2mime.ContainsKey(extension)) { + Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(extension); + if (regKey != null && regKey.GetValue("Content Type") != null) { + extension2mime[extension] = regKey.GetValue("Content Type").ToString(); + } else { + return null; + } + } + } + } + return extension2mime[extension]; + } + } } diff --git a/FLocal.sln b/FLocal.sln index a835f4b..d4fff68 100644 --- a/FLocal.sln +++ b/FLocal.sln @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Importer", "Importer\Import EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImportConsole", "ImportConsole\ImportConsole.csproj", "{208B84AF-A5DD-4C20-83D5-160EA243BA43}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IISUploadHandler", "IISUploadHandler\IISUploadHandler.csproj", "{09F13185-3B2A-4FCB-AB6A-750112FFF920}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -73,6 +75,12 @@ Global {208B84AF-A5DD-4C20-83D5-160EA243BA43}.Release|Any CPU.ActiveCfg = Release|Any CPU {208B84AF-A5DD-4C20-83D5-160EA243BA43}.Release|Any CPU.Build.0 = Release|Any CPU {208B84AF-A5DD-4C20-83D5-160EA243BA43}.Release|x86.ActiveCfg = Release|Any CPU + {09F13185-3B2A-4FCB-AB6A-750112FFF920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09F13185-3B2A-4FCB-AB6A-750112FFF920}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09F13185-3B2A-4FCB-AB6A-750112FFF920}.Debug|x86.ActiveCfg = Debug|Any CPU + {09F13185-3B2A-4FCB-AB6A-750112FFF920}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09F13185-3B2A-4FCB-AB6A-750112FFF920}.Release|Any CPU.Build.0 = Release|Any CPU + {09F13185-3B2A-4FCB-AB6A-750112FFF920}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/IISMainHandler/HandlersFactory.cs b/IISMainHandler/HandlersFactory.cs index 8d30371..425da9e 100644 --- a/IISMainHandler/HandlersFactory.cs +++ b/IISMainHandler/HandlersFactory.cs @@ -14,6 +14,13 @@ namespace FLocal.IISHandler { // throw new FLocalException("Malformed url"); // } if(context.requestParts.Length < 1) return new handlers.RootHandler(); + + #region legacy + if(context.httprequest.Path.ToLower().StartsWith("/user/upload/")) { + return new handlers.response.LegacyUploadHandler(); + } + #endregion + switch(context.requestParts[0].ToLower()) { case "boards": return new handlers.BoardsHandler(); @@ -33,6 +40,8 @@ namespace FLocal.IISHandler { return new handlers.response.UserListHandler(); case "user": return new handlers.response.UserInfoHandler(); + case "uploads": + return new handlers.response.UploadHandler(); case "static": return new handlers.StaticHandler(context.requestParts); case "do": diff --git a/IISMainHandler/IISMainHandler.csproj b/IISMainHandler/IISMainHandler.csproj index 7b8a8b4..fd9821b 100644 --- a/IISMainHandler/IISMainHandler.csproj +++ b/IISMainHandler/IISMainHandler.csproj @@ -50,6 +50,7 @@ + @@ -62,8 +63,10 @@ + + diff --git a/IISMainHandler/MainHandler.cs b/IISMainHandler/MainHandler.cs index 8758d1e..6d00219 100644 --- a/IISMainHandler/MainHandler.cs +++ b/IISMainHandler/MainHandler.cs @@ -12,7 +12,7 @@ namespace FLocal.IISHandler { get { return true; } } - public void ProcessRequest(HttpContext httpcontext) { + private void doProcessRequest(HttpContext httpcontext) { Uri referer = httpcontext.Request.UrlReferrer; if(referer != null && referer.PathAndQuery.StartsWith("/static")) { @@ -32,5 +32,13 @@ namespace FLocal.IISHandler { handler.Handle(context); } + public void ProcessRequest(HttpContext context) { + try { + this.doProcessRequest(context); + } catch(RedirectException e) { + context.Response.Redirect(e.newUrl); + } + } + } } diff --git a/IISMainHandler/exceptions/RedirectException.cs b/IISMainHandler/exceptions/RedirectException.cs new file mode 100644 index 0000000..9af2144 --- /dev/null +++ b/IISMainHandler/exceptions/RedirectException.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace FLocal.IISHandler { + class RedirectException : FLocal.Core.FLocalException { + + public readonly string newUrl; + + public RedirectException(string newUrl) : base("Redirect to " + newUrl + " is being performed") { + this.newUrl = newUrl; + } + + } +} diff --git a/IISMainHandler/handlers/StaticHandler.cs b/IISMainHandler/handlers/StaticHandler.cs index c0ce399..04c4236 100644 --- a/IISMainHandler/handlers/StaticHandler.cs +++ b/IISMainHandler/handlers/StaticHandler.cs @@ -5,7 +5,7 @@ using System.Text; using System.Web; using System.Text.RegularExpressions; using System.IO; -using Microsoft.Win32; +using FLocal.Core; namespace FLocal.IISHandler.handlers { class StaticHandler : ISpecificHandler { @@ -16,23 +16,6 @@ namespace FLocal.IISHandler.handlers { this.requestParts = requestParts; } - private static Dictionary extension2mime = new Dictionary(); - private static string getMimeByExtension(string extension) { - if(!extension2mime.ContainsKey(extension)) { - lock(extension2mime) { - if(!extension2mime.ContainsKey(extension)) { - RegistryKey regKey = Registry.ClassesRoot.OpenSubKey(extension); - if (regKey != null && regKey.GetValue("Content Type") != null) { - extension2mime[extension] = regKey.GetValue("Content Type").ToString(); - } else { - return null; - } - } - } - } - return extension2mime[extension]; - } - public void Handle(WebContext context) { if(this.requestParts.Length < 2) { throw new HttpException(403, "listing not allowed"); @@ -56,7 +39,7 @@ namespace FLocal.IISHandler.handlers { throw new HttpException(403, "forbidden"); } - string mime = getMimeByExtension(fileinfo.Extension); + string mime = Util.getMimeByExtension(fileinfo.Extension); if(mime != null) { context.httpresponse.ContentType = mime; } else { @@ -66,7 +49,7 @@ namespace FLocal.IISHandler.handlers { context.httpresponse.CacheControl = HttpCacheability.Public.ToString(); context.httpresponse.Expires = 1440; - context.httpresponse.WriteFile(fileinfo.FullName); + context.httpresponse.TransmitFile(fileinfo.FullName); } } diff --git a/IISMainHandler/handlers/response/LegacyUploadHandler.cs b/IISMainHandler/handlers/response/LegacyUploadHandler.cs new file mode 100644 index 0000000..6d3a329 --- /dev/null +++ b/IISMainHandler/handlers/response/LegacyUploadHandler.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FLocal.Core; +using FLocal.Common; +using System.Xml.Linq; +using FLocal.Common.dataobjects; + +namespace FLocal.IISHandler.handlers.response { + class LegacyUploadHandler : AbstractGetHandler { + + protected override string templateName { + get { + return null; + } + } + + protected override System.Xml.Linq.XElement[] getSpecificData(WebContext context) { + if(context.requestParts.Length != 3) throw new FLocalException("wrong url"); + string[] parts = context.requestParts[2].Split('.'); + if(parts.Length != 2) throw new FLocalException("wrong url"); + if(parts[0].PHPSubstring(0, 4).ToLower() != "file") throw new FLocalException("wrong url"); + int rawFileNum = int.Parse(parts[0].PHPSubstring(4)); + int fileNum; + switch(parts[1].ToLower()) { + case "jpg": + fileNum = rawFileNum; + break; + case "gif": + fileNum = 500000 + rawFileNum; + break; + case "png": + fileNum = 600000 + rawFileNum; + break; + default: + throw new FLocalException("wrong url"); + } + throw new RedirectException("/Uploads/" + fileNum + "/"); + } + + } +} diff --git a/IISMainHandler/handlers/response/UploadHandler.cs b/IISMainHandler/handlers/response/UploadHandler.cs new file mode 100644 index 0000000..4bea604 --- /dev/null +++ b/IISMainHandler/handlers/response/UploadHandler.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FLocal.Core; +using FLocal.Common; +using System.Xml.Linq; +using FLocal.Common.dataobjects; + +namespace FLocal.IISHandler.handlers.response { + class UploadHandler : AbstractGetHandler { + + protected override string templateName { + get { + return null; + } + } + + protected override System.Xml.Linq.XElement[] getSpecificData(WebContext context) { + if(context.requestParts.Length != 2) throw new FLocalException("wrong url"); + Upload upload = Upload.LoadById(int.Parse(context.requestParts[1])); + throw new RedirectException(Config.instance.UploaderUrl + "Data/" + upload.hash + "." + upload.extension); + } + + } +} diff --git a/IISUploadHandler/Config.cs b/IISUploadHandler/Config.cs new file mode 100644 index 0000000..dee9498 --- /dev/null +++ b/IISUploadHandler/Config.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Configuration; + +namespace FLocal.IISUploadHandler { + class Config { + + public static readonly Config instance = new Config(); + + public readonly string salt; + public readonly string storageDir; + + private Config() { + var appSettings = ConfigurationManager.AppSettings; + this.salt = appSettings["salt"]; + this.storageDir = appSettings["storageDir"]; + } + + } +} diff --git a/IISUploadHandler/IISUploadHandler.csproj b/IISUploadHandler/IISUploadHandler.csproj new file mode 100644 index 0000000..c5c8e05 --- /dev/null +++ b/IISUploadHandler/IISUploadHandler.csproj @@ -0,0 +1,68 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {09F13185-3B2A-4FCB-AB6A-750112FFF920} + Library + Properties + FLocal.IISUploadHandler + IISUploadHandler + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + 3.5 + + + + 3.5 + + + 3.5 + + + + + + + + + + + + {6F532626-E9F8-498E-9683-1538E7CD62CB} + Core + + + + + \ No newline at end of file diff --git a/IISUploadHandler/Properties/AssemblyInfo.cs b/IISUploadHandler/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2a0c3d7 --- /dev/null +++ b/IISUploadHandler/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("IISUploadHandler")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("IISUploadHandler")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("15bb5131-77d1-4703-bded-bbf9a7ffae50")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/IISUploadHandler/UploadHandler.cs b/IISUploadHandler/UploadHandler.cs new file mode 100644 index 0000000..c76b4d4 --- /dev/null +++ b/IISUploadHandler/UploadHandler.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.IO; +using FLocal.Core; + +namespace FLocal.IISUploadHandler { + class UploadHandler : IHttpHandler { + + public bool IsReusable { + get { + return true; + } + } + + public void ProcessRequest(HttpContext httpcontext) { + if(httpcontext.Request.Path.ToLower() == "/upload/") { + this.ProcessUpload(httpcontext); + } else if(httpcontext.Request.Path.ToLower().StartsWith("/data/")) { + this.ProcessRetrieve(httpcontext); + } else { + throw new HttpException(403, "wrong url"); + } + } + + private static string getFilePath(string md5, string extension) { + foreach(char chr in (md5 + extension)) { + if(!Char.IsLetterOrDigit(chr)) throw new HttpException(403, "wrong md5 or extension"); + } + return Config.instance.storageDir + md5.PHPSubstring(0, 2) + Path.DirectorySeparatorChar + md5.PHPSubstring(2, 2) + Path.DirectorySeparatorChar + md5.PHPSubstring(4) + "." + extension; + } + + private static void CreateDirectoryIfNotExists(DirectoryInfo directoryInfo) { + if(!directoryInfo.Exists) { + CreateDirectoryIfNotExists(directoryInfo.Parent); + directoryInfo.Create(); + } + } + + private void ProcessUpload(HttpContext context) { + byte[] data = new byte[context.Request.InputStream.Length]; + if(context.Request.InputStream.Read(data, 0, (int)context.Request.InputStream.Length) != context.Request.InputStream.Length) { + throw new FLocalException("File is not uploaded correctly"); + } + + string file_md5 = Util.md5(context.Request.InputStream); + + string md5 = Util.md5(file_md5 + " " + Config.instance.salt); + if(md5 != context.Request.QueryString["signature"]) { + throw new HttpException(403, "signature mismatch"); + } + + string filePath = getFilePath(file_md5, context.Request.QueryString["extension"]); + CreateDirectoryIfNotExists((new FileInfo(filePath)).Directory); + using(FileStream stream = new FileStream(filePath, FileMode.CreateNew)) { + stream.Write(data, 0, data.Length); + } + + context.Response.Write("OK"); + } + + private void ProcessRetrieve(HttpContext context) { + string[] requestParts = context.Request.Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + if(requestParts.Length != 2) throw new HttpException(403, "wrong url"); + + string[] fileParts = requestParts[1].Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + if(fileParts.Length != 2) throw new HttpException(403, "wrong url"); + + string mime = Util.getMimeByExtension(fileParts[1]); + if(mime == null) throw new FLocalException("unknown extension '" + fileParts[1] + "'"); + context.Response.ContentType = mime; + + context.Response.TransmitFile(getFilePath(fileParts[0], fileParts[1])); + } + + } +} diff --git a/ImportConsole/Program.cs b/ImportConsole/Program.cs index e86c10d..34483ec 100644 --- a/ImportConsole/Program.cs +++ b/ImportConsole/Program.cs @@ -8,6 +8,8 @@ using FLocal.Common.dataobjects; using FLocal.Core; using System.Configuration; using NConsoler; +using System.IO; +using FLocal.Common; namespace FLocal.ImportConsole { class Program { @@ -66,7 +68,63 @@ namespace FLocal.ImportConsole { [Action] public static void ProcessUpload(string pathToUpload) { - throw new NotImplementedException(); + User uploader = User.LoadByName("Guest 127.0.0.1"); + DirectoryInfo directoryInfo = new DirectoryInfo(pathToUpload); + int i=0; + foreach(FileSystemInfo _info in directoryInfo.GetFiles()) { + if(i%100 == 0) { + Console.Write("[" + (int)(i/100) + "]"); + } + FileInfo info = _info as FileInfo; + //Console.WriteLine("Processing " + info.FullName); + if(!info.Name.StartsWith("file")) { + Console.Write("!"); + } else { + string[] parts = info.Name.Split('.'); + if(parts.Length != 2) throw new FLocalException("wrong file name"); + int raw = int.Parse(parts[0].PHPSubstring(4)); + int id; + switch(parts[1].ToLower()) { + case "jpg": + id = raw; + break; + case "gif": + id = 500000 + raw; + break; + case "png": + id = 600000 + raw; + break; + default: + throw new FLocalException("wrong extension"); + } + if(info != null) { + try { + Upload.LoadById(id); + Console.Write("-"); + } catch(NotFoundInDBException) { + try { + UploadManager.UploadFile( + info.OpenRead(), + parts[1], + info.LastWriteTime, + uploader, + id + ); + } catch(UploadManager.AlreadyUploadedException e) { + Console.WriteLine(id + " md5 is equal to that of " + e.uploadId); + Console.ReadLine(); + } catch(Exception e) { + Console.WriteLine(e.GetType().FullName + ": " + e.Message); + Console.WriteLine(e.StackTrace); + throw; + } + Console.Write("+"); + //Console.WriteLine("Processed " + info.FullName); + } + } + } + i++; + } } } } diff --git a/build-all.bat b/build-all.bat index bb3a5f2..a49d4b9 100644 --- a/build-all.bat +++ b/build-all.bat @@ -2,4 +2,6 @@ @rem Note that this script will only produce a .msi packet of your binaries and data. It will not compile your sources. cd Builder Builder IISMainHandler -move /Y IISMainHandler\product.msi ..\IISMainHandler.msi \ No newline at end of file +move /Y IISMainHandler\product.msi ..\IISMainHandler.msi +Builder IISUploadHandler +move /Y IISUploadHandler\product.msi ..\IISUploadHandler.msi \ No newline at end of file