1/** 2 * @file Describe the file 3 * Copyright (c) 2023 Huawei Device Co., Ltd. 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import { isEmptyStr, isUninterrupted } from '@ohos/common/src/main/ets/utils/TextUtils'; 18import { getQueries } from '@ohos/common/src/main/ets/utils/UrlUtils'; 19import { Log } from "@ohos/common/src/main/ets/utils/Log"; 20 21const KEY_RANGE_BEGIN = 'begin'; 22 23const KEY_RANGE_END = 'end'; 24 25const KEY_SEARCH_TEXT = 'searchText'; 26 27const KEY_ORDER_BY_ASC = 'orderByAsc'; 28 29const KEY_ORDER_BY_DESC = 'orderByDesc'; 30 31const TAG = 'InstancesQueryParams'; 32 33// max length (16 chinese) of search text, The excess part will be discarded. 34const MAX_SEARCH_LENGTH = 32; 35 36/** 37 * the class contains Instances query Params 38 * 39 * @since 2022-09-02 40 */ 41export class InstancesQueryParams { 42 begin?: number 43 end?: number 44 searchText?: string 45 orderByAsc?: string 46 orderByDesc?: string 47 48 constructor() { 49 } 50 51 /** 52 * judge is valid begin&end params: 53 * 1.has valid begin&end value 54 * 2.begin&end >0 55 * 3.begin<end 56 * 57 * @param begin the begin params 58 * @param end the end params 59 */ 60 public isValidBeginEnd(): boolean { 61 const begin = this.getBegin(); 62 const end = this.getEnd(); 63 if (begin === null || begin === undefined) { 64 return false; 65 } 66 if (end === null || end === undefined) { 67 return false; 68 } 69 return end >= 0 && end >= 0 && begin < end; 70 } 71 72 public getBegin(): number { 73 return this.begin as number; 74 } 75 76 public setBegin(begin: number): void { 77 this.begin = begin; 78 } 79 80 public getEnd(): number { 81 return this.end as number; 82 } 83 84 public setEnd(end: number): void { 85 this.end = end; 86 } 87 88 public getSearchText(): string { 89 return this.searchText as string; 90 } 91 92 public setSearchText(searchText: string): void { 93 this.searchText = searchText; 94 } 95 96 public getOrderByAsc(): string { 97 return this.orderByAsc as string; 98 } 99 100 public setOrderByAsc(orderByAsc: string): void { 101 this.orderByAsc = orderByAsc; 102 } 103 104 public getOrderByDesc(): string { 105 return this.orderByDesc as string; 106 } 107 108 public setOrderByDesc(orderByDesc: string): void { 109 this.orderByDesc = orderByDesc; 110 } 111} 112 113/** 114 * judge is valid params or not: 115 * 1.invalid object -> false 116 * 2.valid searchText -> true 117 * 3.valid begin and end -> true 118 * 4.or else -> false 119 * 120 * @param params object of InstancesQueryParams 121 */ 122export function isValidQueryParams(params: InstancesQueryParams): boolean { 123 if (params === null || params === undefined) { 124 return false; 125 } 126 const isValidSearchText = !isEmptyStr(params.getSearchText()); 127 if (isValidSearchText) { 128 return true; 129 } 130 return params.isValidBeginEnd(); 131} 132 133/** 134 * parse all params from uri 135 * 136 * @param uri uri from caller 137 * @return result of InstancesQueryParams after parsed 138 */ 139export function parseParamsFromUri(uri: string): InstancesQueryParams | undefined { 140 const queryMap = getQueries(uri); 141 if (queryMap === null || queryMap === undefined || queryMap.size <= 0) { 142 Log.warn(TAG, 'parseBeginEndFromUri get invalid queryMap'); 143 return undefined; 144 } 145 const paramsResult = new InstancesQueryParams(); 146 147 parseBeginEndFromQueryMap(queryMap, paramsResult); 148 149 parseSearchTextFromQueryMap(queryMap, paramsResult); 150 151 parseOrderByFromQueryMap(queryMap, paramsResult); 152 153 return paramsResult; 154} 155 156/** 157 * try parse begin and end params from queryMap 158 * 159 * @param queryMap params map parsed from uri 160 * @param params the object to save parse result 161 */ 162function parseBeginEndFromQueryMap(queryMap: Map<string, string>, params: InstancesQueryParams): void { 163 const beginStr = queryMap.get(KEY_RANGE_BEGIN) as string; 164 const endStr = queryMap.get(KEY_RANGE_END) as string; 165 if (isEmptyStr(beginStr) || isEmptyStr(endStr)) { 166 Log.warn(TAG, 'parseBeginEndFromUri get invalid begin end str'); 167 return; 168 } 169 try { 170 const beginTime = Number.parseInt(beginStr); 171 const endTime = Number.parseInt(endStr); 172 Log.debug(TAG, `parseBeginEndFromUri ${beginTime}/${endTime}`); 173 params.setBegin(beginTime); 174 params.setEnd(endTime); 175 } catch (err) { 176 Log.error(TAG, `parseBeginEndFromUri get err ${err}`); 177 } 178} 179 180/** 181 * try parse search params from queryMap 182 * 183 * @param queryMap params map parsed from uri 184 * @param params the object to save parse result 185 */ 186function parseSearchTextFromQueryMap(queryMap: Map<string, string>, params: InstancesQueryParams): void { 187 let searchText = queryMap.get(KEY_SEARCH_TEXT) as string; 188 if (isEmptyStr(searchText)) { 189 return; 190 } 191 192 // decode for chinese search 193 searchText = decodeURI(searchText); 194 if (isEmptyStr(searchText)) { 195 Log.warn(TAG, `the searchText is empty after decode`); 196 return; 197 } 198 const originalText = searchText; 199 200 // Crop the character string to the maximum length supported by the query. 201 if (searchText.length > MAX_SEARCH_LENGTH) { 202 searchText = searchText.substring(0, MAX_SEARCH_LENGTH); 203 } 204 205 // Replace characters to prevent SQL injection. 206 searchText = getSecuritySearchText(searchText); 207 208 params.setSearchText(searchText); 209 210 if (originalText != searchText) { 211 Log.info(TAG, `the searchText is converted from ${originalText} to ${searchText}`); 212 } 213} 214 215/** 216 * To prevent SQL injection, replace sql's separator characters to double characters. 217 * And you need to add the ' symbol before and after searchText when splicing SQL statements. 218 * 219 * @param searchText search text before replace 220 */ 221function getSecuritySearchText(searchText: string): string { 222 /* 223 * Separator characters of sql: 224 * MySQL -> ` 225 * MS-SQL -> ] 226 * PostgreSQL -> " 227 * String separator -> ' 228 */ 229 return searchText.replace(RegExp("/(?<matchedText>[\`\]\"\'])/gm"), '$<matchedText>$<matchedText>'); 230} 231 232/** 233 * try parse orderBy params from queryMap 234 * 235 * @param queryMap params map parsed from uri 236 * @param params the object to save parse result 237 */ 238function parseOrderByFromQueryMap(queryMap: Map<string, string>, params: InstancesQueryParams): void { 239 const orderByAsc = queryMap.get(KEY_ORDER_BY_ASC) as string; 240 if (isUninterrupted(orderByAsc)) { 241 params.setOrderByAsc(orderByAsc); 242 } 243 const orderByDesc = queryMap.get(KEY_ORDER_BY_DESC) as string; 244 if (isUninterrupted(orderByDesc)) { 245 params.setOrderByDesc(orderByDesc); 246 } 247}