(%ns36jmE318}^F7s#?;ot#vXg
z&F&TVh3vF>8FJIFIJ1qdee~syayQRh6T*dOZ2_+k6KvH3B
zz7;aM%kfr4JLaOo=$uET{PY|A)@*7$;+4g0Mw5)WS$wiZHn-ZwhTVS7W^i7}eKF<=
zcAJe5w>af+2PJCZFSoT5Ria&CFVeyLJYKb9*{TUbmWge_Whyv5kN1w~FLLkQIdAlW
zY)ZoNG~8S(D6si4DwP69?+Zm#E(3J$ZAg#chNB8?8~O%jgeX|ovrpwroKz1_T-oS4
z3T^D%p0_S$hN`bMPQ{@grP+8icxiZw=J|=9O4ppE*)UMCB~lL=Th3BiXC@nHfAHA7
z_7ydg;sHt0I;3jq|41v+ximS+sUNfY5(j_RV9X=ws(#|B+{Y#2}=Bi|c1yialc;wp0Wa1}t!Bp{zqD*A1J|a(p&gr^rCHcz#9DR3<+ZfBiE1lbqsdnXM)k
zwhW<48Elu;Hs3+p(YA*>#)lBAcT@(yFjJ=MkOqhM@7KGj@xa!S?XV;Z-^q$l)F3S2
z0f0$=aU{5C4jo^gTVyrcCF<3k+08%`ynIY>1Vn)`LE?XdF|KE{|IA!Xk6AGGQ6tn$
zhX2MY&w7Q7d*n`vs|Ku-+gE`yh40=N_xmLsxl$}Q{EW!1Yiuld{PxvcOvtw%Xe_*3
zmn*sqZRu5{NLKg~@~G)sBgXF;-_>pn?ACjYnpA#%R&-_Wm>DT4+=GfGqwAzkQiqL^
z)u|4|)bk?zJsPyCc;1OZwYdfH>zngO$
zzk;lY9Zi6SG>vRu&qs7HB12Pb`cmdy*-*qXcv=+x@e$IU6Gl
zE22J!?j0l(^NhU?+@sux!nr(n)V>{3!nXI09S=b_70o_)`UhC!`g+!8a@RUxW*-d5
z4N;7Qmibq;4lMCNm)(^3M$7s64%hB8B
zvKIEjc6k<|=S=+)CC4ptu?$ASMwsIsn06oGa3D*gwQDKglzRdiI{bM_k*p4$Sr3XS_4E
znZ3=djk_Z${>_jwPWQN^eCq(mw6x)a+_#lm1r}DZCz5&a5>LgteXsud5d@1)*bhUa
zt6CLvk_~&(fu{q%pXMdkw0exGy*Gu2!Hl@?d#pEY)X!%>-@JcKDi|YK_=hp`-158gPp84$I$vP7dGOiB|A;_cg1_%=q7^zTRyBZfh*II4<8!
zNrr+Drb7rDSLBO?I&{gct;ncxa>TV1s0SC97PM^5a!JWMTGU_DS#KR_F
zzxNV%m|&|luw?dvnN}IiMxJycckYgI4tb{QGwt$>G;r0*HTRqNQ227h5wSvr&L;|Hv47`a_OKPZ?#YV!kcxVCHUHHu{m6eO!O((h3Jbh{
z`Um(&BBWvPSn#5uZO51~YMAJ5Ct75{wB=vi-+Kb)yer61)a^N*7x;k7VckXYKOa&_
z4;iG(P&o{8YzPaamKo%5P@$^Yy}ZbMGb-yJ_`#LRJhJ=m`46eQSOQ=A8*g3rfG<;!
z@;p%eu=+_v`ZJlVQUmShLljyuf3MaSm##yTnz{I$qOfGrn%Ekr;3%pul%v;6CXIT=
zB~<%VwbFbp#r!@TWCT_+jG5r$e$7xV^WNsUh$-*5fmE=wdCHMa?2}gIjn>A)*Gc>!
zMN8#i!1$I*W|ceaWF7;_a>{bLdNOwBu?F(jPETHDwCNoAf?_QU^YA7BjK|?4cjd2vsLPgkvaJ4_qBpYCoVV
zen}JvOb!IX^@Gln43XI?a83!o!{%toY@jjz%S7S^LeEOKY~}VwUAgH#(wKwR8=h^8
ziobY=OnEPZcHdI$9$?gAyPXbj~eJ8#e=^0Hy
za315p6}qx{>dQ<{)@Wb(vTlU~kcP
zJ@niI7D1FqypDJYmj;fx*D&z~`kDjt0)$kJdM$3lVoS3TRt-aJQ993pZ915!J1FbL+;>f7F}ac;EA|5zTDo^1eSKwmEH^&n)a9LdJ`vE}6wJAaG4*9FYrJD+tkO={2?{z){vjdh
zdaFg!q~$4axPC<)IbG60T9YZfI`!44MfW;rXSxY$O2F>dAdJbd_Bk?Lr_H`2eE_4&
zqT0BLs)U3#oxA=cc$v|qb0p$KsrN{N6_r((HfhSEnwNO0gVUO;7ko(H98x>2t#qnp
z^sX=yMRGCEYLG8fCY>sp&ODDb_vvR<*dZ?AkkI_v>k3<+p_I2}!F27$&TYwY90T2F
zA&=$rIJTfLTois%BC9%oiCNSYT(GJ6;bC&lLiK+!BJV1q;V3JU{YQ+3`QHE$hRlQd
ztganD?19d*^hPBqPCK@d4i!LhrM*PF^5AWCveeOJ;K|O)?7j)M5{n(zchfSLQjOan
zG2j$w8o%vW*_PQoa>uc%0t5J=9)VKHvDgMyn8R-DPCwST7_<9Qo=g!(|E9>W{j0j+
zw?zQLG7Kn7>hw=3z*D==76sozslE3^{Pt4GCEG#_UmofEa=aGHSA;enzL&an99}Zj
zm*C5gP+buZpI#*AsF>GR#gc(pn`t=&4Oa#VedWAeDP1nfJz)iNzh!I5r{V2i-Wa2;
zNE%fb4^$9<-yAFITcse)t%N9m*8Dkj5*bHgw?21T%Yx!eeEZ?mM?0g)=0H6`VbwBl
ztX4DC9s?j3`I&l_syX0gyisE2$)8^#+yPasX@hwJ0pVM{=&rQ8<>5A%36gemFZRXP
zyk2+NIb@aQ2Ly)73*2fe;ZNQff#ZhuXxK+cWdj=lQ#GD>2@T%V^2sNuV=tOuqMp{r
zT!)Lz$Cop{*aF9WHeYt7m&j`wj(%Iu6x{yY&r7a*yUujeT>}
zT>WOh0>*D9W_Y!|Yufhw)E0aToV&%_t!b?08P
zlXH-nq0Ej|4y#1OG4{avaMS`{=c0z9c*x`sUxNXpao6Y89+HvKW8IL7HEx#(Wg07Sr!^t&LR
zqak#KslcH2{`Pn=)jq2tuFa!C*Ss;28{s%Fs`KCHb1>>@bcStzUFg1u0?Fp2Q2EPf
zhy)tZn9wCf1v%R^L4dtxEu{uoy8M1a*B)Qqz%4^5m7pu_+j&@Nh=CRwH5gG%mzM&WsH3p1^SY}|;;jYtnNtPZnz-}v@J
zb0Y3K6>i5FnZJg4U`i+nLzMAPT|rSC@%ryyU!ShQNF~}i>NM==kVNhA)3l=CO)J(N>jlHuc
z81Nr%6KEf=SMp0`JDx35QkE3lX;GMR*27pPv!Ni0H6ONnKo#dZc!>}WdX&{{#23y<
zkEfpf1K`RR@>M+{SL$<&5pA9J@>G;eKeLqVQJ#BjiSk_iD6F23)xet?ccn3w^(Kau
zm8ud^%aPtvX@_D5R-PlD(z&>mQRMA}#d>Qj)EW6yXNcERJ1>3s@a}Le%`yO=V#UAL
z2GiXF&y}?H=8jKUDzBfLEh^~RekF!VLT?!w8^@%Xn$0~FBnka^BRNE$lo#l2ZaX&A
z`w+>2BjSiJ_Y!2NT?f|Uf_vJ|dOkW?b~^-l=~>hV9swnpC@#jnRDO+E`RgVp&%SN@
z;lY%DRlBL2vtD!UvROi&hEgu$bjr^XOyd~JCuU4(*48C{hPxX3Gya1aC|nWW?Ee=o
zh)3FV205|eucZ*wSOA@xeb97tuvNcQ^*xI%>UePlHx%q*MfkjvjRSuQ6U`=jBQ0uXpN!y_L66{dZ)Dd8o^xw;nl<@tKcY
zQ!ACQopude(s|LWX-=pp*t8TlBW2cux3}Unvz8=DAAAeHj26H7TtKL`F_Td+@G$a=
z^2myFw9?PTCb&6h?myhfJie`HzWx@gvf?fyl^6f_Hw|a+QAuU@KGGcpadNuT8`nB#
z4PY*!TMk;gzrJ)1S;S$@SuH<8qNrz}JT?gi$%Dd6z(Vz@oID2J-7G!Mk!+|7+UZ
zxm3KPt7^%M0Ui{XpvBNqD}?#rr}~*MiT}+)_2}Mvu;2sg=Vm+?)YK2du>W~h=0V>6
z|4AD7|4>QzzYhP;Z3X|!?*DV@)Bhj(%poP(KZL2KTJugDW5vsmaa4zqk6E;x$`Y-B
z*<*^LP}LGSSZ9T$l>lgu9%=F)m4@M#zJBOHjF_Gkd_;frs6P_KU~!Tg}-MIH8Wh5@Z0$2X*}T0dIjhD-P^M4!388-g8^SKGY`_&}PQrT-SCG(k!H
z`S|$M)YQ(Qi8Y-o}Q**svbY@etGZjXOs{Job2P9
z;I%F2=(s&xZ1MK?o|&0}AFmv5Y;16-%gH?hs%2
z69%+3YUW&$6kQ3`vVdFzqMeiA6j&F-^8TlD0aB%&7(~qckM7
z5Kb?^&b@ItYW~>^u%gh`uy7)>;FI8w@Gh;$GZ8FQywy7%A)agFe^6<+p%sG@r*ffsW52tr_#-kSL-~_WAk)Qb2s9$F
zG$SdqV%;mdXL9deFu#@^4dqgAxhzB9Fjk&yH@J%(+0?eDyi7Igm_@Z|>)982hDTZ{
zV}C>pcL;PZg$p^l!FzMElIJO;mJAr!*ozHzV@Jy!O^uB~KwvmuqVh(7igKi28X0zV
z!qy=gZ7dj36mE$48)@$B%T351Mol&E3)LB=uir@PkiYAB*n!f?IZV^|cgV~kscD(j
zsmZ@@YNz5$%$$Z*rM{tUVrp>CkoOn-=gM3G#F3$;W4b@T1M(Pz6Or
z=<;$7q=W@DIc~=yiqWe;$ainA3Nv25?t)9vz?fn5{r!^Zfk`bJV`;E+0#Z~A(2&^T
z*L-_7=NI3wqfntQr)AK$e-bmdQ8-Rp=3M8$(0!Q^%~gEG@IGPxf1A_$AFI|ZTJ^Gu
zm;E6tr{5flgZxg($;tNi_P=KFBn59+QOUOO>7i}+WqXBuxF=p&?gLJ@iEIm(mOp!x
zohki3MQ8VfHe^Y^i;iy?(i#)kKL&QM>!
z6*oU@cWZt6>=|$;OIz#5bN?q!oS2uFm$GR?z>C91Kf!q%Xk}GiaBy_Bxz*}_)2FZe
z_WDXl+dA9d7AlIy%6ca*-^#eV!}rer-3FWe)4E;23AuyUDe2U_bZuXuI@@Uq1IdgvG-QkDGn#re=F_7Cb8E>rk5@86}uzjm3|cjYdRYgS)p^GINt
z?k+D|m;-=0$@A8{lRKZ*S6_+iT(Mgwu3T}=yPS%e||%*r2$-
zchkgy5y&rZSF&b%{(ZYw&Ii4x>wT2gRFQg#D2)!Ov4c|!u)wIBUR_i6lI{AfuX?5`
zpRd`hRQ$T!`)t#5OSJ}=V
z&oBOaUoAZ@_sNwNz5SM7uRk`7o&(%>n)Xj)*1lySPd52SNB@3vb8}{9=9kL64+T38#rEl>X;|0D{Gns7Xik}ZICf0Wdc#>
zf@h_B=GQRyUhN022uv^r>9A~`@I?6O->_5i<$r5F^l!cmUb)+_
zC~lB!5^!kw<9Ooq@>{1a-B}X;uDoxp+~1e!_H!#j9iumS0(T6}2F9q%o&&bj#OuHz;uR
zVgW&tt6z%D3l5s|W>a;@Yd_$25Khoehe^u8_LFX<0(TZcjGF>n{JHnn=1)fI=am03
z@rRd6n2@Nn0V4JdroDy=X>{{-M~WlKpHRw3igV%yq$Lc
zvCSj>fLF`xmLyMKXnZ|eI=+<4BHB{Z*AM;I-c2x#4XS=KJju=;g{!q+|99aGVeF#m9c7ww6mcVSnGlf0;aJWY(W1qIdU8@0|gfl
zZv)!Wap3<#xrSU|&Q#L-{Umt0dyY0Dy>{$rauVXy-f_KEM-wF_`wZI7Vs6{k2^uhZ`_wGI+ZBxvX {
+
+ @Override
+ public String getColumnName() {
+ return "User";
+ }
+
+ @Override
+ public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
+ ServiceProvider provider) throws IllegalArgumentException {
+ if (serverInfo.hasDefaultLogin()) {
+ if (serverInfo.getDBType() == DBType.postgres) {
+ BSimPostgresDataSource ds =
+ BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo);
+ if (ds != null) {
+ return ds.getUserName();
+ }
+ }
+ // TODO: how can we determine elastic username?
+ return "";
+ }
+ String info = serverInfo.getUserName();
+ boolean hasPassword = serverInfo.hasPassword();
+ if (hasPassword) {
+ info = info + ":****"; // show w/masked password
+ }
+ return info;
+ }
+
+ @Override
+ public int getColumnPreferredWidth() {
+ return 100;
+ }
+ }
+
private static class HostColumn
extends AbstractDynamicTableColumn {
@@ -176,7 +217,6 @@ public class BSimServerTableModel extends GDynamicColumnTableModel checkForValidDialog());
+ }
+
+ private static final String HOSTNAME_IP_REGEX =
+ "^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*$";
+ private static final Pattern HOSTNAME_IP_PATTERN = Pattern.compile(HOSTNAME_IP_REGEX);
+
+ private void createHostField() {
+
+ hostField = new GFormattedTextField(FORMATTER_FACTORY, "");
+ hostField.setName("Host");
+ hostField.setText("");
+ hostField.setDefaultValue("");
+ hostField.setIsError(true);
+ hostField.setEditable(true);
+
+ hostField.setInputVerifier(new InputVerifier() {
+ @Override
+ public boolean verify(JComponent input) {
+ setStatus("");
+ String hostname = hostField.getText().trim();
+ if (hostname.length() == 0) {
+ setStatus("");
+ return false;
+ }
+ Matcher hostMatch = HOSTNAME_IP_PATTERN.matcher(hostname);
+ if (!hostMatch.matches()) {
+ setStatus("Unsupported host name or IP address");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean shouldYieldFocus(JComponent source, JComponent target) {
+ return true;
+ }
+ });
+
+ hostField.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ e.consume();
+ hostField.setText("");
+ hostField.setDefaultValue("");
+ hostField.setIsError(true);
+ }
+ checkForValidDialog();
+ }
+ });
+
+ hostField.addTextEntryStatusListener(f -> checkForValidDialog());
+ }
+
+ // NOTE: Username pattern based on PostgreSQL restrictions
+ private static final String USERNAME_REGEX = "^[a-zA-Z_][a-zA-Z0-9_$]*$";
+ private static final Pattern USERNAME_PATTERN = Pattern.compile(USERNAME_REGEX);
+
+ private void createUserField() {
+
+ userField = new GFormattedTextField(FORMATTER_FACTORY, "");
+ userField.setName("User");
+ userField.setText("");
+ userField.setDefaultValue("");
+ userField.setEditable(true);
+
+ userField.setInputVerifier(new InputVerifier() {
+ @Override
+ public boolean verify(JComponent input) {
+ setStatus("");
+ String username = userField.getText().trim();
+ if (username.length() == 0) {
+ setStatus("");
+ return true;
+ }
+ Matcher userMatch = USERNAME_PATTERN.matcher(username);
+ if (!userMatch.matches()) {
+ setStatus("Unsupported database user name");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean shouldYieldFocus(JComponent source, JComponent target) {
+ return true;
+ }
+ });
+
+ userField.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ e.consume();
+ userField.setText("");
+ userField.setDefaultValue("");
+ userField.setIsError(false);
+ }
+ checkForValidDialog();
+ }
+ });
+
+ userField.addTextEntryStatusListener(f -> checkForValidDialog());
+ }
+
@Override
BSimServerInfo getServerInfo() {
+ if (nameField.getTextEntryStatus() == Status.INVALID ||
+ userField.getTextEntryStatus() == Status.INVALID ||
+ hostField.getTextEntryStatus() == Status.INVALID) {
+ return null;
+ }
+
+ String user = userField.getText().trim();
+ if (ClientUtil.getUserName().equals(user)) {
+ user = null;
+ }
+
String name = nameField.getText().trim();
String host = hostField.getText().trim();
+
int port = getPort(portField.getText().trim());
if (name.isBlank() || host.isBlank() || port < 0) {
return null;
}
- return new BSimServerInfo(type, host, port, name);
+
+ return new BSimServerInfo(type, user, host, port, name);
}
}
@@ -291,7 +488,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
if (file.isDirectory()) {
return null;
}
- return new BSimServerInfo(DBType.file, null, -1, path);
+ return new BSimServerInfo(path);
}
}
@@ -303,24 +500,24 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
public NotifyingTextField(String initialText) {
super(20);
setText(initialText);
- getDocument().addDocumentListener(new DocumentListener() {
+ getDocument().addDocumentListener(new MyFieldListener());
+ }
+ }
- @Override
- public void insertUpdate(DocumentEvent e) {
- checkForValidDialog();
- }
+ class MyFieldListener implements DocumentListener {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ checkForValidDialog();
+ }
- @Override
- public void removeUpdate(DocumentEvent e) {
- checkForValidDialog();
- }
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ checkForValidDialog();
+ }
- @Override
- public void changedUpdate(DocumentEvent e) {
- checkForValidDialog();
- }
-
- });
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ checkForValidDialog();
}
}
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java
index bd3ac7176c..cf912e746a 100644
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java
@@ -25,7 +25,6 @@ import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import org.apache.commons.dbcp2.BasicDataSource;
-import org.apache.commons.lang3.StringUtils;
import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.features.bsim.query.FunctionDatabase.ConnectionType;
@@ -283,10 +282,12 @@ public class BSimPostgresDBConnectionManager {
*/
private Connection connect() throws SQLException, CancelledException {
- String userName = bds.getUsername();
- bds.setUsername(StringUtils.isBlank(userName) ? ClientUtil.getUserName() : userName);
- bds.setPassword(null);
- connectionType = ConnectionType.SSL_No_Authentication;
+ String loginError = null;
+
+ serverInfo.setUserInfo(bds);
+
+ connectionType = serverInfo.hasPassword() ? ConnectionType.SSL_Password_Authentication
+ : ConnectionType.SSL_No_Authentication;
try {
// Specify SSL connection properties
setSSLProperties();
@@ -299,6 +300,10 @@ public class BSimPostgresDBConnectionManager {
if (e.getMessage().contains("password-based authentication") ||
e.getMessage().contains("SCRAM-based") ||
e.getMessage().contains("password authentication failed")) {
+ if (serverInfo.hasPassword()) {
+ loginError = "Access denied: " + serverInfo;
+ Msg.error(this, loginError);
+ }
// Use Ghidra's authentication infrastructure
connectionType = ConnectionType.SSL_Password_Authentication; // Try again with a password
// fallthru to second attempt at getConnection
@@ -319,7 +324,6 @@ public class BSimPostgresDBConnectionManager {
" idle=" + bds.getNumIdle());
}
- String loginError = null;
while (true) {
ClientAuthenticator clientAuthenticator = null;
if (connectionType == ConnectionType.SSL_Password_Authentication) {
@@ -327,9 +331,11 @@ public class BSimPostgresDBConnectionManager {
if (clientAuthenticator == null) { // Make sure authenticator is registered
throw new SQLException("No registered authenticator");
}
- NameCallback nameCb = new NameCallback("User ID:");
- nameCb.setName(bds.getUsername());
- PasswordCallback passCb = new PasswordCallback("Password:", false);
+ NameCallback nameCb = new NameCallback("User ID:", bds.getUsername());
+ if (!serverInfo.hasDefaultLogin()) {
+ nameCb.setName(bds.getUsername());
+ }
+ PasswordCallback passCb = new PasswordCallback(" ", false); // force use of default prompting
try {
if (!clientAuthenticator.processPasswordCallbacks(
"BSim Database Authentication", "BSim Database Server",
@@ -338,9 +344,8 @@ public class BSimPostgresDBConnectionManager {
}
bds.setPassword(new String(passCb.getPassword()));
// User may have specified new username, or this may return NULL
- userName = nameCb.getName();
- if (!StringUtils.isBlank(userName)) {
- bds.setUsername(userName);
+ if (serverInfo.hasDefaultLogin()) {
+ bds.setUsername(nameCb.getName());
}
}
finally {
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java
index 2fe9cfbc74..05ccc40925 100644
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,12 +16,15 @@
package ghidra.features.bsim.query;
import java.io.Closeable;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.*;
+import java.nio.charset.StandardCharsets;
import java.util.Objects;
+import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.StringUtils;
+import ghidra.framework.client.ClientUtil;
+
public class BSimServerInfo implements Comparable {
/**
@@ -48,6 +51,7 @@ public class BSimServerInfo implements Comparable {
}
private final DBType dbType;
+ private final String userinfo; // username[:password]
private final String host;
private final int port;
private final String dbName;
@@ -56,6 +60,62 @@ public class BSimServerInfo implements Comparable {
/**
* Construct a new {@link BSimServerInfo} object
+ *
+ * @param dbType BSim DB type
+ * @param userinfo connection user info, {@code username[:password]} (ignored for {@link DBType#file}).
+ * If blank, {@link ClientUtil#getUserName()} is used.
+ * @param host host name (ignored for {@link DBType#file})
+ * @param port port number (ignored for {@link DBType#file})
+ * @param dbName name of database (simple database name except for {@link DBType#file}
+ * which should reflect an absolute file path. On Windows OS the path may start with a
+ * drive letter.
+ * @throws IllegalArgumentException if invalid arguments are specified
+ */
+ public BSimServerInfo(DBType dbType, String userinfo, String host, int port, String dbName) {
+ Objects.requireNonNull(dbType, "DBType must be specified");
+ this.dbType = dbType;
+
+ if ((dbType == DBType.postgres || dbType == DBType.elastic) && StringUtils.isEmpty(host)) {
+ throw new IllegalArgumentException("host required");
+ }
+
+ dbName = dbName.trim();
+ if (StringUtils.isEmpty(dbName)) {
+ throw new IllegalArgumentException("Non-empty dbName required");
+ }
+
+ if (dbType == DBType.file) {
+ host = null;
+ port = -1;
+ userinfo = null;
+ dbName = cleanupFilename(dbName);
+ }
+ else {
+ if (dbName.contains("/") || dbName.contains("\\")) { // may want additional validation
+ throw new IllegalArgumentException("Invalid " + dbType + " dbName: " + dbName);
+ }
+ userinfo = cleanupUserInfo(userinfo);
+ if (port <= 0) {
+ port = -1;
+ }
+ if (dbType == DBType.postgres && port <= 0) {
+ port = DEFAULT_POSTGRES_PORT;
+ }
+ if (dbType == DBType.elastic && port <= 0) {
+ port = DEFAULT_ELASTIC_PORT;
+ }
+ }
+
+ this.userinfo = userinfo;
+ this.host = host;
+ this.port = port;
+ this.dbName = dbName;
+ }
+
+ /**
+ * Construct a new {@link BSimServerInfo} object. For non-file database the user's defaut
+ * username is used (see {@link ClientUtil#getUserName()}).
+ *
* @param dbType BSim DB type
* @param host host name (ignored for {@link DBType#file})
* @param port port number (ignored for {@link DBType#file})
@@ -65,49 +125,34 @@ public class BSimServerInfo implements Comparable {
* @throws IllegalArgumentException if invalid arguments are specified
*/
public BSimServerInfo(DBType dbType, String host, int port, String dbName) {
- Objects.requireNonNull(dbType, "DBType must be specified");
- this.dbType = dbType;
-
- if ((dbType == DBType.postgres || dbType == DBType.elastic) && StringUtils.isEmpty(host)) {
- throw new IllegalArgumentException("host required");
- }
- this.host = host;
-
- if (port <= 0) {
- port = -1;
- }
- if (dbType == DBType.postgres && port <= 0) {
- port = DEFAULT_POSTGRES_PORT;
- }
- if (dbType == DBType.elastic && port <= 0) {
- port = DEFAULT_ELASTIC_PORT;
- }
- this.port = port;
+ this(dbType, null, host, port, dbName);
+ }
+ /**
+ * Construct a new {@link BSimServerInfo} object for a {@link DBType#file} type database.
+ *
+ * @param dbName name of database which should reflect an absolute file path.
+ * On Windows OS the path may start with a drive letter.
+ * @throws IllegalArgumentException if invalid arguments are specified
+ */
+ public BSimServerInfo(String dbName) {
+ dbType = DBType.file;
+ userinfo = null;
+ host = null;
+ port = -1;
dbName = dbName.trim();
if (StringUtils.isEmpty(dbName)) {
throw new IllegalArgumentException("Non-empty dbName required");
}
- if (dbType == DBType.file) {
- // transform dbName into acceptable H2 DB file path
- dbName = dbName.replace("\\", "/");
- if ((!dbName.startsWith("/") && !isWindowsFilePath(dbName)) || dbName.endsWith("/")) {
- throw new IllegalArgumentException("Invalid absolute file path: " + dbName);
- }
- if (!dbName.endsWith(H2_FILE_EXTENSION)) {
- dbName += H2_FILE_EXTENSION;
- }
- }
- else if (dbName.contains("/") || dbName.contains("\\")) { // may want additional validation
- throw new IllegalArgumentException("Invalid " + dbType + " dbName: " + dbName);
- }
- this.dbName = dbName;
+ this.dbName = cleanupFilename(dbName);
}
/**
* Construct a new {@link BSimServerInfo} object from a suitable database URL
* (i.e., {@code postgresql:}, {@code https:}, {@code elastic:}, {@code file:}).
- * @param url supported BSim database URL
+ *
+ * @param url supported BSim database URL. For non-file URLs, the hostname or
+ * address may be preceeded by a DB username (e.g., postgresql://user@host:port/dbname
* @throws IllegalArgumentException if unsupported URL protocol specified
*/
public BSimServerInfo(URL url) throws IllegalArgumentException {
@@ -118,18 +163,21 @@ public class BSimServerInfo implements Comparable {
if (protocol.equals("postgresql")) {
t = DBType.postgres;
host = checkURLField(url.getHost(), "host");
+ userinfo = getURLUserInfo(url);
int p = url.getPort();
port = p <= 0 ? DEFAULT_POSTGRES_PORT : p;
}
else if (protocol.equals("https") || protocol.equals("elastic")) {
t = DBType.elastic;
host = checkURLField(url.getHost(), "host");
+ userinfo = getURLUserInfo(url);
int p = url.getPort();
port = p <= 0 ? DEFAULT_ELASTIC_PORT : p;
}
else if (protocol.startsWith("file")) {
t = DBType.file;
host = null;
+ userinfo = null;
port = -1;
if (!"".equals(url.getHost())) {
throw new IllegalArgumentException("Remote file URL not supported: " + url);
@@ -146,7 +194,7 @@ public class BSimServerInfo implements Comparable {
}
path = path.substring(1).strip();
}
- path = checkURLField(path, "path");
+ path = urlDecode(checkURLField(path, "path"));
if (dbType == DBType.file) {
if (path.endsWith("/")) {
throw new IllegalArgumentException("Missing DB filepath in URL: " + url);
@@ -162,6 +210,53 @@ public class BSimServerInfo implements Comparable