diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 745874d..b2113ea 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -53,4 +53,3 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} - TAURI_KEY_PASSWORD: '' diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4c64b67..cba71a3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -53,7 +53,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} - TAURI_KEY_PASSWORD: '' with: tagName: ${{ github.ref_name }} # This only works if your workflow triggers on new tags. projectPath: xcmd-tauri diff --git a/Cargo.lock b/Cargo.lock index efc0819..8e2cd35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,9 +167,11 @@ dependencies = [ "rustls-webpki", "tokio", "tokio-rustls 0.23.4", + "tokio-rustls 0.24.1", "tokio-util", "tracing", - "webpki-roots", + "webpki-roots 0.22.6", + "webpki-roots 0.25.2", ] [[package]] @@ -3649,6 +3651,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pem" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +dependencies = [ + "base64 0.21.4", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -4059,6 +4071,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" +dependencies = [ + "pem", + "ring 0.16.20", + "time", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -6202,6 +6226,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "webpki-roots" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" + [[package]] name = "webview2-com" version = "0.19.1" @@ -6703,6 +6733,9 @@ dependencies = [ "actix-web", "futures-util", "parking_lot 0.12.1", + "rcgen", + "rustls 0.21.7", + "rustls-pemfile", "serde", "serde_derive", "serde_json", @@ -6801,6 +6834,15 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "zbus" version = "3.14.1" diff --git a/systemicons/rustfmt.toml b/systemicons/rustfmt.toml new file mode 100644 index 0000000..12d797c --- /dev/null +++ b/systemicons/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = false diff --git a/systemicons/src/linux/mod.rs b/systemicons/src/linux/mod.rs index a55dd4b..208c7ad 100644 --- a/systemicons/src/linux/mod.rs +++ b/systemicons/src/linux/mod.rs @@ -1 +1,2 @@ - pub mod request; \ No newline at end of file + +pub mod request; diff --git a/systemicons/src/linux/request.rs b/systemicons/src/linux/request.rs index 418134a..eb19b67 100644 --- a/systemicons/src/linux/request.rs +++ b/systemicons/src/linux/request.rs @@ -1,8 +1,15 @@ -use std::{ffi::{CStr, CString, c_void}, fs::{self, File}, io::Read}; use gio_sys::GThemedIcon; use glib::{gobject_ffi::g_object_unref, object::GObject}; use glib_sys::g_free; -use gtk_sys::{GTK_ICON_LOOKUP_NO_SVG, GtkIconTheme, gtk_icon_info_get_filename, gtk_icon_theme_choose_icon, gtk_icon_theme_get_default}; +use gtk_sys::{ + gtk_icon_info_get_filename, gtk_icon_theme_choose_icon, gtk_icon_theme_get_default, + GtkIconTheme, GTK_ICON_LOOKUP_NO_SVG, +}; +use std::{ + ffi::{c_void, CStr, CString}, + fs::{self, File}, + io::Read, +}; use crate::{Error, InnerError}; @@ -13,7 +20,7 @@ pub fn get_icon(ext: &str, size: i32) -> Result, Error> { let mut f = File::open(&filename)?; let metadata = fs::metadata(&filename)?; let mut buffer = vec![0; metadata.len() as usize]; - f.read(&mut buffer)?; + f.read(&mut buffer)?; Ok(buffer) } @@ -33,13 +40,22 @@ pub fn get_icon_as_file(ext: &str, size: i32) -> Result { let theme = gtk_icon_theme_get_default(); if theme.is_null() { println!("You have to initialize GTK!"); - return Err(Error{ message: "You have to initialize GTK!".to_string(), inner_error: InnerError::GtkInitError}) + return Err(Error { + message: "You have to initialize GTK!".to_string(), + inner_error: InnerError::GtkInitError, + }); } let theme = gtk_icon_theme_get_default(); DEFAULT_THEME = Some(theme); } - let icon_names = gio_sys::g_themed_icon_get_names(icon as *mut GThemedIcon) as *mut *const i8; - let icon_info = gtk_icon_theme_choose_icon(DEFAULT_THEME.unwrap(), icon_names, size, GTK_ICON_LOOKUP_NO_SVG); + let icon_names = + gio_sys::g_themed_icon_get_names(icon as *mut GThemedIcon) as *mut *const i8; + let icon_info = gtk_icon_theme_choose_icon( + DEFAULT_THEME.unwrap(), + icon_names, + size, + GTK_ICON_LOOKUP_NO_SVG, + ); let filename = gtk_icon_info_get_filename(icon_info); let res_str = CStr::from_ptr(filename); result = res_str.to_str()?.to_string(); @@ -48,5 +64,6 @@ pub fn get_icon_as_file(ext: &str, size: i32) -> Result { Ok(result) } -pub fn init() { gtk::init().unwrap(); } - +pub fn init() { + gtk::init().unwrap(); +} diff --git a/systemicons/src/windows/mod.rs b/systemicons/src/windows/mod.rs index 484336b..be9378d 100644 --- a/systemicons/src/windows/mod.rs +++ b/systemicons/src/windows/mod.rs @@ -1,2 +1 @@ pub mod request; - diff --git a/systemicons/src/windows/request.rs b/systemicons/src/windows/request.rs index 0ae9007..24bb614 100644 --- a/systemicons/src/windows/request.rs +++ b/systemicons/src/windows/request.rs @@ -1,23 +1,25 @@ +use crate::Error; use image::ImageFormat; -use std::{mem, ptr, time, io}; -use winapi::{STRUCT, - shared::{minwindef::LPVOID, - windef::{HGDIOBJ, HBITMAP, HICON} +use std::{io, mem, ptr, time}; +use winapi::{ + shared::{ + minwindef::LPVOID, + windef::{HBITMAP, HGDIOBJ, HICON}, + }, + um::{ + shellapi::{ + ExtractIconExW, SHGetFileInfoW, SHFILEINFOW, SHGFI_ICON, SHGFI_LARGEICON, + SHGFI_SMALLICON, SHGFI_TYPENAME, SHGFI_USEFILEATTRIBUTES, }, - um::{ - wingdi::{BITMAPINFOHEADER, GetObjectW, BITMAP, PBITMAP, DeleteObject, GetBitmapBits}, - winnt::{HANDLE, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL}, - winuser::{ GetIconInfo, ICONINFO, DestroyIcon}, - shellapi::{ - SHGFI_TYPENAME, SHGFI_USEFILEATTRIBUTES, SHGFI_ICON, SHGetFileInfoW, SHFILEINFOW, SHGFI_LARGEICON, SHGFI_SMALLICON, ExtractIconExW - } - } - }; -use crate::Error; + wingdi::{DeleteObject, GetBitmapBits, GetObjectW, BITMAP, BITMAPINFOHEADER, PBITMAP}, + winnt::{FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, HANDLE}, + winuser::{DestroyIcon, GetIconInfo, ICONINFO}, + }, + STRUCT, +}; pub fn get_icon(ext: &str, size: i32, dir: bool) -> Result, Error> { - - fn get_icon_from_ext(ext: &str, size: i32, dir: bool)->HICON { + fn get_icon_from_ext(ext: &str, size: i32, dir: bool) -> HICON { unsafe { let p_path = utf_16_null_terminiated(ext); let mut file_info = SHFILEINFOW { @@ -25,13 +27,29 @@ pub fn get_icon(ext: &str, size: i32, dir: bool) -> Result, Error> { hIcon: ptr::null_mut(), iIcon: 0, szDisplayName: [0 as u16; 260], - szTypeName: [0; 80] + szTypeName: [0; 80], }; let file_info_size = mem::size_of_val(&file_info) as u32; for _ in 0..3 { // Sporadically this method returns 0! - SHGetFileInfoW(p_path.as_ptr(), if dir { FILE_ATTRIBUTE_DIRECTORY } else { FILE_ATTRIBUTE_NORMAL }, &mut file_info, file_info_size, - SHGFI_ICON | SHGFI_USEFILEATTRIBUTES | SHGFI_TYPENAME | if size > 16 { SHGFI_LARGEICON } else { SHGFI_SMALLICON }); + SHGetFileInfoW( + p_path.as_ptr(), + if dir { + FILE_ATTRIBUTE_DIRECTORY + } else { + FILE_ATTRIBUTE_NORMAL + }, + &mut file_info, + file_info_size, + SHGFI_ICON + | SHGFI_USEFILEATTRIBUTES + | SHGFI_TYPENAME + | if size > 16 { + SHGFI_LARGEICON + } else { + SHGFI_SMALLICON + }, + ); if file_info.hIcon != ptr::null_mut() { break; } else { @@ -43,7 +61,7 @@ pub fn get_icon(ext: &str, size: i32, dir: bool) -> Result, Error> { } } - fn extract_icon(path: &str, size: i32)->HICON { + fn extract_icon(path: &str, size: i32) -> HICON { unsafe { let mut icon_large: HICON = ptr::null_mut(); let mut icon_small: HICON = ptr::null_mut(); @@ -75,16 +93,46 @@ pub fn get_icon(ext: &str, size: i32, dir: bool) -> Result, Error> { if icon == ptr::null_mut() { icon = extract_icon("C:\\Windows\\system32\\SHELL32.dll", size); } - let mut icon_info = ICONINFO{ fIcon: 0, hbmColor: ptr::null_mut(), hbmMask: ptr::null_mut(), xHotspot: 0, yHotspot: 0 }; + let mut icon_info = ICONINFO { + fIcon: 0, + hbmColor: ptr::null_mut(), + hbmMask: ptr::null_mut(), + xHotspot: 0, + yHotspot: 0, + }; GetIconInfo(icon, &mut icon_info); DestroyIcon(icon); - let mut bmp_color = BITMAP { bmBits: ptr::null_mut(), bmBitsPixel: 0, bmHeight: 0, bmPlanes: 0, bmType: 0, bmWidth: 0, bmWidthBytes: 0}; - GetObjectW(icon_info.hbmColor as HANDLE, mem::size_of_val(&bmp_color) as i32, &mut bmp_color as PBITMAP as LPVOID); - let mut bmp_mask = BITMAP { bmBits: ptr::null_mut(), bmBitsPixel: 0, bmHeight: 0, bmPlanes: 0, bmType: 0, bmWidth: 0, bmWidthBytes: 0}; - GetObjectW(icon_info.hbmMask as HANDLE, mem::size_of_val(&bmp_mask) as i32, &mut bmp_mask as PBITMAP as LPVOID); + let mut bmp_color = BITMAP { + bmBits: ptr::null_mut(), + bmBitsPixel: 0, + bmHeight: 0, + bmPlanes: 0, + bmType: 0, + bmWidth: 0, + bmWidthBytes: 0, + }; + GetObjectW( + icon_info.hbmColor as HANDLE, + mem::size_of_val(&bmp_color) as i32, + &mut bmp_color as PBITMAP as LPVOID, + ); + let mut bmp_mask = BITMAP { + bmBits: ptr::null_mut(), + bmBitsPixel: 0, + bmHeight: 0, + bmPlanes: 0, + bmType: 0, + bmWidth: 0, + bmWidthBytes: 0, + }; + GetObjectW( + icon_info.hbmMask as HANDLE, + mem::size_of_val(&bmp_mask) as i32, + &mut bmp_mask as PBITMAP as LPVOID, + ); - fn get_bitmap_count(bitmap: &BITMAP)->i32 { + fn get_bitmap_count(bitmap: &BITMAP) -> i32 { let mut n_width_bytes = bitmap.bmWidthBytes; // bitmap scanlines MUST be a multiple of 4 bytes when stored // inside a bitmap resource, so round up if necessary @@ -101,7 +149,11 @@ pub fn get_icon(ext: &str, size: i32, dir: bool) -> Result, Error> { let bitmap_bytes_count = get_bitmap_count(&bmp_color) as usize; let mask_bytes_count = get_bitmap_count(&bmp_mask) as usize; - let complete_size = icon_header_size + icon_dir_size + info_header_size + bitmap_bytes_count + mask_bytes_count; + let complete_size = icon_header_size + + icon_dir_size + + info_header_size + + bitmap_bytes_count + + mask_bytes_count; let image_bytes_count = bitmap_bytes_count + mask_bytes_count; let mut bytes = Vec::::with_capacity(complete_size); @@ -110,7 +162,7 @@ pub fn get_icon(ext: &str, size: i32, dir: bool) -> Result, Error> { let iconheader = ICONHEADER { id_count: 1, // number of ICONDIRs id_reserved: 0, - id_type: 1// Type 1 = ICON (type 2 = CURSOR) + id_type: 1, // Type 1 = ICON (type 2 = CURSOR) }; let byte_ptr: *mut u8 = mem::transmute(&iconheader); ptr::copy_nonoverlapping(byte_ptr, bytes.as_mut_ptr(), icon_header_size); @@ -131,7 +183,7 @@ pub fn get_icon(ext: &str, size: i32, dir: bool) -> Result, Error> { w_planes: bmp_color.bmPlanes, w_bit_count: bmp_color.bmBitsPixel, dw_image_offset: (icon_header_size + 16) as u32, - dw_bytes_in_res: (mem::size_of::() + image_bytes_count) as u32 + dw_bytes_in_res: (mem::size_of::() + image_bytes_count) as u32, }; let byte_ptr: *mut u8 = mem::transmute(&icon_dir); @@ -149,20 +201,28 @@ pub fn get_icon(ext: &str, size: i32, dir: bool) -> Result, Error> { biClrUsed: 0, biCompression: 0, biXPelsPerMeter: 0, - biYPelsPerMeter: 0 + biYPelsPerMeter: 0, }; let byte_ptr: *mut u8 = mem::transmute(&bi_header); ptr::copy_nonoverlapping(byte_ptr, bytes[pos..].as_mut_ptr(), info_header_size); let pos = pos + info_header_size; // write the RGBQUAD color table (for 16 and 256 colour icons) - if bmp_color.bmBitsPixel == 2 || bmp_color.bmBitsPixel == 8 { } + if bmp_color.bmBitsPixel == 2 || bmp_color.bmBitsPixel == 8 {} - write_icon_data_to_memory(&mut bytes[pos..], icon_info.hbmColor, - &bmp_color, bitmap_bytes_count as usize); + write_icon_data_to_memory( + &mut bytes[pos..], + icon_info.hbmColor, + &bmp_color, + bitmap_bytes_count as usize, + ); let pos = pos + bitmap_bytes_count as usize; - write_icon_data_to_memory(&mut bytes[pos..], icon_info.hbmMask, - &bmp_mask, mask_bytes_count as usize); + write_icon_data_to_memory( + &mut bytes[pos..], + icon_info.hbmMask, + &bmp_mask, + mask_bytes_count as usize, + ); let im = image::load_from_memory(&bytes)?; let mut png_bytes: io::Cursor> = io::Cursor::new(Vec::new()); @@ -179,13 +239,13 @@ fn utf_16_null_terminiated(x: &str) -> Vec { x.encode_utf16().chain(std::iter::once(0)).collect() } -STRUCT!{#[debug] struct ICONHEADER { +STRUCT! {#[debug] struct ICONHEADER { id_reserved: i16, id_type: i16, id_count: i16, }} -STRUCT!{#[debug] struct ICONDIR { +STRUCT! {#[debug] struct ICONDIR { b_width: u8, b_height: u8, b_color_count: u8, @@ -196,12 +256,21 @@ STRUCT!{#[debug] struct ICONDIR { dw_image_offset: u32, // file-offset to the start of ICONIMAGE }} -fn write_icon_data_to_memory(mem: &mut [u8], h_bitmap: HBITMAP, bmp: &BITMAP, bitmap_byte_count: usize) { +fn write_icon_data_to_memory( + mem: &mut [u8], + h_bitmap: HBITMAP, + bmp: &BITMAP, + bitmap_byte_count: usize, +) { unsafe { let mut icon_data = Vec::::with_capacity(bitmap_byte_count); icon_data.set_len(bitmap_byte_count); - GetBitmapBits(h_bitmap, bitmap_byte_count as i32, icon_data.as_mut_ptr() as LPVOID); + GetBitmapBits( + h_bitmap, + bitmap_byte_count as i32, + icon_data.as_mut_ptr() as LPVOID, + ); // bitmaps are stored inverted (vertically) when on disk.. // so write out each line in turn, starting at the bottom + working @@ -211,13 +280,21 @@ fn write_icon_data_to_memory(mem: &mut [u8], h_bitmap: HBITMAP, bmp: &BITMAP, bi for i in (0..bmp.bmHeight).rev() { // Write the bitmap scanline - ptr::copy_nonoverlapping(icon_data[(i * bmp.bmWidthBytes) as usize..].as_ptr(), mem[pos..].as_mut_ptr(), bmp.bmWidthBytes as usize); // 1 line of BYTES + ptr::copy_nonoverlapping( + icon_data[(i * bmp.bmWidthBytes) as usize..].as_ptr(), + mem[pos..].as_mut_ptr(), + bmp.bmWidthBytes as usize, + ); // 1 line of BYTES pos += bmp.bmWidthBytes as usize; // extend to a 32bit boundary (in the file) if necessary if bmp.bmWidthBytes & 3 != 0 { let padding: [u8; 4] = [0; 4]; - ptr::copy_nonoverlapping(padding.as_ptr(), mem[pos..].as_mut_ptr(), (4 - bmp.bmWidthBytes) as usize); + ptr::copy_nonoverlapping( + padding.as_ptr(), + mem[pos..].as_mut_ptr(), + (4 - bmp.bmWidthBytes) as usize, + ); pos += 4 - bmp.bmWidthBytes as usize; } } diff --git a/xcmd-base/Cargo.toml b/xcmd-base/Cargo.toml index ae20889..e94da38 100644 --- a/xcmd-base/Cargo.toml +++ b/xcmd-base/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -actix-web = { version = "4.4", features = ["rustls"] } +actix-web = { version = "4.4", features = ["rustls-0_21"] } actix-cors = "0.6" futures-util = "0.3" parking_lot = "0.12" @@ -13,6 +13,9 @@ serde_json = "1.0" serde_derive = "1.0" sysinfo = "0.29" systemicons = { path = "../systemicons", optional = true } +rcgen = "0.11" +rustls = "0.21" +rustls-pemfile = "1.0" tracing = { version = "0.1", features = ["log"] } tracing-futures = "0.2" diff --git a/xcmd-base/src/lib.rs b/xcmd-base/src/lib.rs index 3418693..9624d9c 100644 --- a/xcmd-base/src/lib.rs +++ b/xcmd-base/src/lib.rs @@ -10,10 +10,16 @@ use actix_web::{ }; use futures_util::future::LocalBoxFuture; use parking_lot::Mutex; +use rcgen::generate_simple_self_signed; +use rustls::{Certificate, PrivateKey, ServerConfig}; +use rustls_pemfile::{certs, pkcs8_private_keys}; use serde::Deserialize; use serde_derive::Serialize; -use std::future::{ready, Ready}; use std::{env, error::Error, net::TcpListener, thread, time::Duration}; +use std::{ + future::{ready, Ready}, + io::BufReader, +}; use sysinfo::{ProcessExt, System, SystemExt}; pub fn get_port() -> Result> { @@ -273,3 +279,39 @@ where Box::pin(async move { Ok(fut.await?) }) } } + +pub fn load_rustls_config() -> Result> { + let config = ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth(); + + let subject_alt_names = vec!["tauri.localhost".to_string(), "localhost".to_string()]; + let cert = generate_simple_self_signed(subject_alt_names)?; + let public_key_pem = cert.serialize_pem().unwrap(); + let private_key_pem = cert.serialize_private_key_pem(); + + let key_file = &mut BufReader::new(private_key_pem.as_bytes()); + let cert_file = &mut BufReader::new(public_key_pem.as_bytes()); + + // convert files to key/cert objects + let cert_chain = certs(cert_file)? + .into_iter() + .map(Certificate) + .collect::>(); + let mut keys: Vec = pkcs8_private_keys(key_file)? + .into_iter() + .map(PrivateKey) + .collect(); + + // exit if no keys could be parsed + if keys.is_empty() { + eprintln!("Could not locate PKCS 8 private keys."); + std::process::exit(1); + } + + let mut result = config.with_single_cert(cert_chain, keys.remove(0))?; + + result.alpn_protocols = vec!["h2".to_string().into(), "http/1.1".to_string().into()]; + + Ok(result) +} diff --git a/xcmd-fs/src/main.rs b/xcmd-fs/src/main.rs index 73d9bc0..b58aa38 100644 --- a/xcmd-fs/src/main.rs +++ b/xcmd-fs/src/main.rs @@ -111,6 +111,8 @@ async fn main() -> Result<(), Box> { .service(enact) }) .bind(("127.0.0.1", port))? + // TODO: use TLS when tauri adds support to trust self-signed certificates + // .bind_rustls_021(format!("127.0.0.1:{}", port), load_rustls_config()?)? .run(); post_startup(&server, port); diff --git a/xcmd-ssh/src/main.rs b/xcmd-ssh/src/main.rs index f4db50a..b9f5c3e 100644 --- a/xcmd-ssh/src/main.rs +++ b/xcmd-ssh/src/main.rs @@ -2,11 +2,11 @@ use actix_web::body::to_bytes; use actix_web::{get, post, web, App, HttpResponse, HttpServer}; use rust_embed::RustEmbed; use ssh2::{FileStat, Session, Sftp}; -use tracing_actix_web::TracingLogger; use std::env; use std::error::Error; use std::net::TcpStream; use std::sync::Arc; +use tracing_actix_web::TracingLogger; use xcmd_base::{ get_port, init_telemetry, post_startup, FileInfo, ListRequest, ListResponse, Middleware, Request, Response, diff --git a/xcmd-tauri/dist/vtable/vtable.js b/xcmd-tauri/dist/vtable/vtable.js index 327e557..9088db6 100644 --- a/xcmd-tauri/dist/vtable/vtable.js +++ b/xcmd-tauri/dist/vtable/vtable.js @@ -62,6 +62,7 @@ class RemoteDataSource { baseUri() { const { port } = this.config; + // TODO: use TLS when tauri adds support to trust self-signed certificates return 'http://localhost:' + port; }