diff --git a/WebApi.cs b/WebApi.cs index 2d0d92d8..144e463b 100644 --- a/WebApi.cs +++ b/WebApi.cs @@ -1,9 +1,10 @@ -using CefSharp; +using CefSharp; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Runtime.Serialization.Formatters.Binary; +using System.Text; using System.Threading; namespace VRCX @@ -152,6 +153,50 @@ namespace VRCX } } + if (options.TryGetValue("uploadImage", out object uploadImage) == true) + { + request.Method = "POST"; + string boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x"); + request.ContentType = "multipart/form-data; boundary=" + boundary; + Stream requestStream = request.GetRequestStream(); + if (options.TryGetValue("postData", out object postDataObject) == true) + { + Dictionary postData = new Dictionary(); + postData.Add("data", (string)postDataObject); + string FormDataTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n"; + foreach (string key in postData.Keys) + { + string item = String.Format(FormDataTemplate, boundary, key, postData[key]); + byte[] itemBytes = System.Text.Encoding.UTF8.GetBytes(item); + requestStream.Write(itemBytes, 0, itemBytes.Length); + } + } + var imageData = options["imageData"] as string; + byte[] fileToUpload = Convert.FromBase64CharArray(imageData.ToCharArray(), 0, imageData.Length); + string fileFormKey = "image"; + string fileName = "image.png"; + string fileMimeType = "image/png"; + string HeaderTemplate = "--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n"; + string header = String.Format(HeaderTemplate, boundary, fileFormKey, fileName, fileMimeType); + byte[] headerbytes = Encoding.UTF8.GetBytes(header); + requestStream.Write(headerbytes, 0, headerbytes.Length); + using (MemoryStream fileStream = new MemoryStream(fileToUpload)) + { + byte[] buffer = new byte[1024]; + int bytesRead = 0; + while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) + { + requestStream.Write(buffer, 0, bytesRead); + } + fileStream.Close(); + } + byte[] newlineBytes = Encoding.UTF8.GetBytes("\r\n"); + requestStream.Write(newlineBytes, 0, newlineBytes.Length); + byte[] endBytes = System.Text.Encoding.UTF8.GetBytes("--" + boundary + "--"); + requestStream.Write(endBytes, 0, endBytes.Length); + requestStream.Close(); + } + try { using (var response = await request.GetResponseAsync() as HttpWebResponse) diff --git a/html/src/app.js b/html/src/app.js index be45c22d..3983a1cc 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -360,9 +360,7 @@ speechSynthesis.getVoices(); if (typeof req !== 'undefined') { return req; } - } else if (init.VRCPlusIcon) { - delete init.VRCPlusIcon; - console.log(init); + } else if (init.uploadImage) { } else { init.headers = { 'Content-Type': 'application/json;charset=utf-8', @@ -8734,20 +8732,30 @@ speechSynthesis.getVoices(); return false; }; - // requres decoding base64 body on C# side $app.methods.onFileChangeVRCPlusIcon = function (e) { var files = e.target.files || e.dataTransfer.files; if (!files.length) { return; } + if (files[0].size >= 10485760) { //10MB + $app.$message({ + message: 'File size too large', + type: 'error' + }); + return; + } + if (!files[0].type.match(/image.*/)) { + $app.$message({ + message: 'File isn\'t an image', + type: 'error' + }); + return; + } var r = new FileReader(); r.onload = function () { - var bodyStart = '---------------------------26696829785232761561272838397\nContent-Disposition: form-data; name="file"; filename="blob"\nContent-Type: image/png\n\n'; - var bodyEnd = '\n---------------------------26696829785232761561272838397--\n'; - var body = bodyStart + r.result + bodyEnd; - var base64Body = btoa(body); + var base64Body = btoa(r.result); API.uploadVRCPlusIcon(base64Body).then((args) => { - this.$message({ + $app.$message({ message: 'Icon uploaded', type: 'success' }); @@ -8757,14 +8765,14 @@ speechSynthesis.getVoices(); r.readAsBinaryString(files[0]); }; + $app.methods.displayVRCPlusIconUpload = function () { + document.getElementById('VRCPlusIconUploadButton').click(); + }; + API.uploadVRCPlusIcon = function (params) { return this.call('icon', { - method: 'POST', - headers: { - 'Content-Type': 'multipart/form-data; boundary=-------------------------26696829785232761561272838397' - }, - VRCPlusIcon: true, - body: params + uploadImage: true, + imageData: params }).then((json) => { var args = { json, diff --git a/html/src/index.pug b/html/src/index.pug index 94c51247..4ce4274e 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -536,11 +536,16 @@ html template(#content) span Clear results el-button(type="default" @click="VRCPlusIconsTable = {}" size="mini" icon="el-icon-delete" circle style="margin-left:0") + el-tooltip(placement="top") + template(#content) + span Upload icon + div(style="display:inline-block") + el-button(type="default" @click="displayVRCPlusIconUpload" size="mini" icon="el-icon-upload2" circle style="margin-left:0") + input(type="file" multiple accept="image/*" @change="onFileChangeVRCPlusIcon" id="VRCPlusIconUploadButton" style="display:none") el-tooltip(placement="top") template(#content) span Reset icon - el-button(type="default" @click="setVRCPlusIcon('')" size="mini" icon="el-icon-close" circle style="margin:0" :disabled="!API.currentUser.userIcon") - //- input(type="file" @change="onFileChangeVRCPlusIcon") + el-button(type="default" @click="setVRCPlusIcon('')" size="mini" icon="el-icon-close" circle style="margin-left:0" :disabled="!API.currentUser.userIcon") br .x-friend-item(v-for="icon in VRCPlusIconsTable" :key="icon.id" style="display:inline-block;margin-top:10px;cursor:default") .vrcplus-icon(style="" @click="setVRCPlusIcon(icon.id)" :class="{ 'current-vrcplus-icon': compareCurrentVRCPlusIcon(icon.id) }")