guacamole 源码_guacamole实现上传下载
看到首先服務端向客戶端發送了filesystem請求,緊接著瀏覽器向服務端發送了get請求,并且后面帶有根目錄標識(“/”)。
1. 源碼解讀
查看指令
/**
* Handlers for all instruction opcodes receivable by a Guacamole protocol
* client.
* @private
*/
var instructionHandlers = {
...其它指令
"filesystem" : function handleFilesystem(parameters) {
var objectIndex = parseInt(parameters[0]);
var name = parameters[1];
// Create object, if supported
if (guac_client.onfilesystem) {
//這里實例化一個object,并且傳遞給客戶端監聽的onfilesystem方法
var object = objects[objectIndex] = new Guacamole.Object(guac_client, objectIndex);
guac_client.onfilesystem(object, name);
}
// If unsupported, simply ignore the availability of the filesystem
},
...其它指令
}
查看實例化的object源碼
/**
* An object used by the Guacamole client to house arbitrarily-many named
* input and output streams.
*
* @constructor
* @param {Guacamole.Client} client
* The client owning this object.
*
* @param {Number} index
* The index of this object.
*/
Guacamole.Object = function guacamoleObject(client, index) {
/**
* Reference to this Guacamole.Object.
*
* @private
* @type {Guacamole.Object}
*/
var guacObject = this;
/**
* Map of stream name to corresponding queue of callbacks. The queue of
* callbacks is guaranteed to be in order of request.
*
* @private
* @type {Object.}
*/
var bodyCallbacks = {};
/**
* Removes and returns the callback at the head of the callback queue for
* the stream having the given name. If no such callbacks exist, null is
* returned.
*
* @private
* @param {String} name
* The name of the stream to retrieve a callback for.
*
* @returns {Function}
* The next callback associated with the stream having the given name,
* or null if no such callback exists.
*/
var dequeueBodyCallback = function dequeueBodyCallback(name) {
// If no callbacks defined, simply return null
var callbacks = bodyCallbacks[name];
if (!callbacks)
return null;
// Otherwise, pull off first callback, deleting the queue if empty
var callback = callbacks.shift();
if (callbacks.length === 0)
delete bodyCallbacks[name];
// Return found callback
return callback;
};
/**
* Adds the given callback to the tail of the callback queue for the stream
* having the given name.
*
* @private
* @param {String} name
* The name of the stream to associate with the given callback.
*
* @param {Function} callback
* The callback to add to the queue of the stream with the given name.
*/
var enqueueBodyCallback = function enqueueBodyCallback(name, callback) {
// Get callback queue by name, creating first if necessary
var callbacks = bodyCallbacks[name];
if (!callbacks) {
callbacks = [];
bodyCallbacks[name] = callbacks;
}
// Add callback to end of queue
callbacks.push(callback);
};
/**
* The index of this object.
*
* @type {Number}
*/
this.index = index;
/**
* Called when this object receives the body of a requested input stream.
* By default, all objects will invoke the callbacks provided to their
* requestInputStream() functions based on the name of the stream
* requested. This behavior can be overridden by specifying a different
* handler here.
*
* @event
* @param {Guacamole.InputStream} inputStream
* The input stream of the received body.
*
* @param {String} mimetype
* The mimetype of the data being received.
*
* @param {String} name
* The name of the stream whose body has been received.
*/
this.onbody = function defaultBodyHandler(inputStream, mimetype, name) {
// Call queued callback for the received body, if any
var callback = dequeueBodyCallback(name);
if (callback)
callback(inputStream, mimetype);
};
/**
* Called when this object is being undefined. Once undefined, no further
* communication involving this object may occur.
*
* @event
*/
this.onundefine = null;
/**
* Requests read access to the input stream having the given name. If
* successful, a new input stream will be created.
*
* @param {String} name
* The name of the input stream to request.
*
* @param {Function} [bodyCallback]
* The callback to invoke when the body of the requested input stream
* is received. This callback will be provided a Guacamole.InputStream
* and its mimetype as its two only arguments. If the onbody handler of
* this object is overridden, this callback will not be invoked.
*/
this.requestInputStream = function requestInputStream(name, bodyCallback) {
// Queue body callback if provided
if (bodyCallback)
enqueueBodyCallback(name, bodyCallback);
// Send request for input stream
client.requestObjectInputStream(guacObject.index, name);
};
/**
* Creates a new output stream associated with this object and having the
* given mimetype and name. The legality of a mimetype and name is dictated
* by the object itself.
*
* @param {String} mimetype
* The mimetype of the data which will be sent to the output stream.
*
* @param {String} name
* The defined name of an output stream within this object.
*
* @returns {Guacamole.OutputStream}
* An output stream which will write blobs to the named output stream
* of this object.
*/
this.createOutputStream = function createOutputStream(mimetype, name) {
return client.createObjectOutputStream(guacObject.index, mimetype, name);
};
};
讀取下官方的注釋,關于此類的定義:
An object used by the Guacamole client to house arbitrarily-many named input and output streams.
我們需要操作的應該就是input 和 output stream,下面我們進行下猜測
1> this.onbody對應的方法應該就是我們需要實際處理inputStream的地方,
2> this.requestInputStream后面調用了client.requestObjectInputStream(guacObject.index, name);方法,源碼如下:
Guacamole.Client = function(tunnel) {
...其它內容
this.requestObjectInputStream = function requestObjectInputStream(index, name) {
// Do not send requests if not connected
if (!isConnected())
return;
tunnel.sendMessage("get", index, name);
};
...其它內容
}
可以看出這個方法就是向服務端方發送get請求。我們上面分析websocket請求的時候,提到過向客戶端發送過這樣一個請求,并且在一直監聽的onbody方法中應該能收到服務器返回的響應。
3> this.createOutputStream應該是創建了一個通往guacamole服務器的stream,我們上傳文件的時候可能會用到這個stream,調用了client.createObjectOutputStream(guacObject.index, mimetype, name);方法,其源碼如下:
Guacamole.Client = function(tunnel) {
...其它內容
/**
* Creates a new output stream associated with the given object and having
* the given mimetype and name. The legality of a mimetype and name is
* dictated by the object itself. The instruction necessary to create this
* stream will automatically be sent.
*
* @param {Number} index
* The index of the object for which the output stream is being
* created.
*
* @param {String} mimetype
* The mimetype of the data which will be sent to the output stream.
*
* @param {String} name
* The defined name of an output stream within the given object.
*
* @returns {Guacamole.OutputStream}
* An output stream which will write blobs to the named output stream
* of the given object.
*/
this.createObjectOutputStream = function createObjectOutputStream(index, mimetype, name) {
// 得到了stream,并向服務端發送了put請求
// Allocate and ssociate stream with object metadata
var stream = guac_client.createOutputStream();
tunnel.sendMessage("put", index, stream.index, mimetype, name);
return stream;
};
...其它內容
}
繼續往下追diamante, 這句var stream = guac_client.createOutputStream(); 源碼如下:
Guacamole.Client = function(tunnel) {
...其它內容
/**
* Allocates an available stream index and creates a new
* Guacamole.OutputStream using that index, associating the resulting
* stream with this Guacamole.Client. Note that this stream will not yet
* exist as far as the other end of the Guacamole connection is concerned.
* Streams exist within the Guacamole protocol only when referenced by an
* instruction which creates the stream, such as a "clipboard", "file", or
* "pipe" instruction.
*
* @returns {Guacamole.OutputStream}
* A new Guacamole.OutputStream with a newly-allocated index and
* associated with this Guacamole.Client.
*/
this.createOutputStream = function createOutputStream() {
// Allocate index
var index = stream_indices.next();
// Return new stream
var stream = output_streams[index] = new Guacamole.OutputStream(guac_client, index);
return stream;
};
...其它內容
}
再繼續,new Guacamole.OutputStream(guac_client, index);源碼:
/**
* Abstract stream which can receive data.
*
* @constructor
* @param {Guacamole.Client} client The client owning this stream.
* @param {Number} index The index of this stream.
*/
Guacamole.OutputStream = function(client, index) {
/**
* Reference to this stream.
* @private
*/
var guac_stream = this;
/**
* The index of this stream.
* @type {Number}
*/
this.index = index;
/**
* Fired whenever an acknowledgement is received from the server, indicating
* that a stream operation has completed, or an error has occurred.
*
* @event
* @param {Guacamole.Status} status The status of the operation.
*/
this.onack = null;
/**
* Writes the given base64-encoded data to this stream as a blob.
*
* @param {String} data The base64-encoded data to send.
*/
this.sendBlob = function(data) {
//發送數據到服務端,并且數據格式應該為該base64-encoded data格式,分塊傳輸過去的
client.sendBlob(guac_stream.index, data);
};
/**
* Closes this stream.
*/
this.sendEnd = function() {
client.endStream(guac_stream.index);
};
};
到此,我們可以知道this.createOutputStream是做的是事情就是建立了一個通往服務器的stream通道,并且,我們可以操作這個通道發送分塊數據(stream.sendBlob方法)。
2. 上傳下載的核心代碼
關于文件系統和下載的代碼
var fileSystem;
//初始化文件系統
client.onfilesystem = function(object){
fileSystem=object;
//監聽onbody事件,對返回值進行處理,返回內容可能有兩種,一種是文件夾,一種是文件。
object.onbody = function(stream, mimetype, filename){
stream.sendAck('OK', Guacamole.Status.Code.SUCCESS);
downloadFile(stream, mimetype, filename);
}
}
//連接有滯后,初始化文件系統給個延遲
setTimeout(function(){
//從根目錄開始,想服務端發送get請求
let path = '/';
fileSystem.requestInputStream(path);
}, 5000);
downloadFile = (stream, mimetype, filename) => {
//使用blob reader處理數據
var blob_builder;
if (window.BlobBuilder) blob_builder = new BlobBuilder();
else if (window.WebKitBlobBuilder) blob_builder = new WebKitBlobBuilder();
else if (window.MozBlobBuilder) blob_builder = new MozBlobBuilder();
else
blob_builder = new (function() {
var blobs = [];
/** @ignore */
this.append = function(data) {
blobs.push(new Blob([data], {"type": mimetype}));
};
/** @ignore */
this.getBlob = function() {
return new Blob(blobs, {"type": mimetype});
};
})();
// 收到blob的處理,因為收到的可能是一塊一塊的數據,需要把他們整合,這里用到了blob_builder
stream.onblob = function(data) {
// Convert to ArrayBuffer
var binary = window.atob(data);
var arrayBuffer = new ArrayBuffer(binary.length);
var bufferView = new Uint8Array(arrayBuffer);
for (var i=0; i
bufferView[i] = binary.charCodeAt(i);
blob_builder.append(arrayBuffer);
length += arrayBuffer.byteLength;
// Send success response
stream.sendAck("OK", 0x0000);
};
// 結束后的操作
stream.onend = function(){
//獲取整合后的數據
var blob_data = blob_builder.getBlob();
//數據傳輸完成后進行下載等處理
if(mimetype.indexOf('stream-index+json') != -1){
//如果是文件夾,需要解決如何將數據讀出來,這里使用filereader讀取blob數據,最后得到一個json格式數據
var blob_reader = new FileReader();
blob_reader.addEventListener("loadend", function() {
let folder_content = JSON.parse(blob_reader.result)
//這里加入自己代碼,實現文件目錄的ui,重新組織當前文件目錄
});
blob_reader.readAsBinaryString(blob_data);
} else {
//如果是文件,直接下載,但是需要解決個問題,就是如何下載blob數據
//借鑒了https://github.com/eligrey/FileSaver.js這個庫
var file_arr = filename.split("/");
var download_file_name = file_arr[file_arr.length - 1];
saveAs(blob_data, download_file_name);
}
}
}
感受下console.log(blob_data)和 console.log(folder_data)的內容如下
關于上傳的代碼
const input = document.getElementById('file-input');
input.onchange = function() {
const file = input.files[0];
//上傳開始
uploadFile(fileSystem, file);
};
uploadFile = (object, file) => {
const _this = this;
const fileUpload = {};
//需要讀取文件內容,使用filereader
const reader = new FileReader();
var current_path = $("#header_title").text(); //上傳到堡壘機的目錄,可以自己動態獲取
var STREAM_BLOB_SIZE = 4096;
reader.onloadend = function fileContentsLoaded() {
//上面源碼分析過,這里先創建一個連接服務端的數據通道
const stream = object.createOutputStream(file.type, current_path + '/' + file.name);
const bytes = new Uint8Array(reader.result);
let offset = 0;
let progress = 0;
fileUpload.name = file.name;
fileUpload.mimetype = file.type;
fileUpload.length = bytes.length;
stream.onack = function ackReceived(status) {
if (status.isError()) {
//提示錯誤信息
//layer.msg(status.message);
return false;
}
const slice = bytes.subarray(offset, offset + STREAM_BLOB_SIZE);
const base64 = bufferToBase64(slice);
// Write packet
stream.sendBlob(base64);
// Advance to next packet
offset += STREAM_BLOB_SIZE;
if (offset >= bytes.length) {
stream.sendEnd();
}
}
};
reader.readAsArrayBuffer(file);
return fileUpload;
};
function bufferToBase64(buf) {
var binstr = Array.prototype.map.call(buf, function (ch) {
return String.fromCharCode(ch);
}).join('');
return btoa(binstr);
}
總結
以上是生活随笔為你收集整理的guacamole 源码_guacamole实现上传下载的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 记一次云服务器配置mysql 远程连接失
- 下一篇: CTF新手抓包找flag