1/*
2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "cmd_parser.h"
17#include <algorithm>
18#include "resconfig_parser.h"
19#include "resource_util.h"
20#include "select_compile_parse.h"
21
22namespace OHOS {
23namespace Global {
24namespace Restool {
25using namespace std;
26const struct option PackageParser::CMD_OPTS[] = {
27    { "inputPath", required_argument, nullptr, Option::INPUTPATH },
28    { "packageName", required_argument, nullptr, Option::PACKAGENAME },
29    { "outputPath", required_argument, nullptr, Option::OUTPUTPATH },
30    { "resHeader", required_argument, nullptr, Option::RESHEADER },
31    { "forceWrite", no_argument, nullptr, Option::FORCEWRITE },
32    { "version", no_argument, nullptr, Option::VERSION},
33    { "modules", required_argument, nullptr, Option::MODULES },
34    { "json", required_argument, nullptr, Option::JSON },
35    { "startId", required_argument, nullptr, Option::STARTID },
36    { "fileList", required_argument, nullptr, Option::FILELIST },
37    { "append", required_argument, nullptr, Option::APPEND },
38    { "combine", required_argument, nullptr, Option::COMBINE },
39    { "dependEntry", required_argument, nullptr, Option::DEPENDENTRY },
40    { "help", no_argument, nullptr, Option::HELP},
41    { "ids", required_argument, nullptr, Option::IDS},
42    { "defined-ids", required_argument, nullptr, Option::DEFINED_IDS},
43    { "icon-check", no_argument, nullptr, Option::ICON_CHECK},
44    { "target-config", required_argument, nullptr, Option::TARGET_CONFIG},
45    { "defined-sysids", required_argument, nullptr, Option::DEFINED_SYSIDS},
46    { "compressed-config", required_argument, nullptr, Option::COMPRESSED_CONFIG},
47    { 0, 0, 0, 0},
48};
49
50const string PackageParser::CMD_PARAMS = ":i:p:o:r:m:j:e:l:x:fhvz";
51
52uint32_t PackageParser::Parse(int argc, char *argv[])
53{
54    InitCommand();
55    if (ParseCommand(argc, argv) != RESTOOL_SUCCESS) {
56        return RESTOOL_ERROR;
57    }
58    if (CheckParam() != RESTOOL_SUCCESS) {
59        return RESTOOL_ERROR;
60    }
61    AdaptResourcesDirForInput();
62    return RESTOOL_SUCCESS;
63}
64
65const vector<string> &PackageParser::GetInputs() const
66{
67    return inputs_;
68}
69
70const string &PackageParser::GetPackageName() const
71{
72    return packageName_;
73}
74
75const string &PackageParser::GetOutput() const
76{
77    return output_;
78}
79
80const vector<string> &PackageParser::GetResourceHeaders() const
81{
82    return resourceHeaderPaths_;
83}
84
85bool PackageParser::GetForceWrite() const
86{
87    return forceWrite_;
88}
89
90const vector<string> &PackageParser::GetModuleNames() const
91{
92    return moduleNames_;
93}
94
95const string &PackageParser::GetConfig() const
96{
97    return configPath_;
98}
99
100const string &PackageParser::GetRestoolPath() const
101{
102    return restoolPath_;
103}
104
105uint32_t PackageParser::GetStartId() const
106{
107    return startId_;
108}
109
110const string &PackageParser::GetDependEntry() const
111{
112    return dependEntry_;
113}
114
115const vector<std::string> &PackageParser::GetSysIdDefinedPaths() const
116{
117    return sysIdDefinedPaths_;
118}
119
120uint32_t PackageParser::AddInput(const string& argValue)
121{
122    string inputPath = ResourceUtil::RealPath(argValue);
123    if (inputPath.empty()) {
124        cerr << "Error: invalid input '" << argValue << "'" << endl;
125        return RESTOOL_ERROR;
126    }
127
128    auto ret = find_if(inputs_.begin(), inputs_.end(), [inputPath](auto iter) {return inputPath == iter;});
129    if (ret != inputs_.end()) {
130        cerr << "Error: repeat input '" << argValue << "'" << endl;
131        return RESTOOL_ERROR;
132    }
133
134    if (!IsAscii(inputPath)) {
135        return RESTOOL_ERROR;
136    }
137    inputs_.push_back(inputPath);
138    return RESTOOL_SUCCESS;
139}
140
141uint32_t PackageParser::AddSysIdDefined(const std::string& argValue)
142{
143    string sysIdDefinedPath = ResourceUtil::RealPath(argValue);
144    if (sysIdDefinedPath.empty()) {
145        cerr << "Error: invalid system id_defined.json path: '" << argValue << "'" << endl;
146        return RESTOOL_ERROR;
147    }
148
149    auto ret = find_if(sysIdDefinedPaths_.begin(), sysIdDefinedPaths_.end(),
150        [sysIdDefinedPath](auto iter) {return sysIdDefinedPath == iter;});
151    if (ret != sysIdDefinedPaths_.end()) {
152        cerr << "Error: repeat system id_defined.json path: '" << argValue << "'" << endl;
153        return RESTOOL_ERROR;
154    }
155
156    if (!IsAscii(sysIdDefinedPath)) {
157        return RESTOOL_ERROR;
158    }
159    sysIdDefinedPaths_.push_back(sysIdDefinedPath);
160    return RESTOOL_SUCCESS;
161}
162
163uint32_t PackageParser::AddPackageName(const string& argValue)
164{
165    if (!packageName_.empty()) {
166        cerr << "Error: double package name " << packageName_ << " vs " << argValue << endl;
167        return RESTOOL_ERROR;
168    }
169
170    packageName_ = argValue;
171    return RESTOOL_SUCCESS;
172}
173
174uint32_t PackageParser::AddOutput(const string& argValue)
175{
176    if (!output_.empty()) {
177        cerr << "Error: double output " << output_ << " vs " << argValue << endl;
178        return RESTOOL_ERROR;
179    }
180
181    output_ = ResourceUtil::RealPath(argValue);
182    if (output_.empty()) {
183        cerr << "Error: invalid output '" << argValue << "'" << endl;
184        return RESTOOL_ERROR;
185    }
186    if (!IsAscii(output_)) {
187        return RESTOOL_ERROR;
188    }
189    return RESTOOL_SUCCESS;
190}
191
192uint32_t PackageParser::AddResourceHeader(const string& argValue)
193{
194    if (find(resourceHeaderPaths_.begin(), resourceHeaderPaths_.end(), argValue) != resourceHeaderPaths_.end()) {
195        cerr << "Error: '" << argValue << "' input duplicated." << endl;
196        return RESTOOL_ERROR;
197    }
198    resourceHeaderPaths_.push_back(argValue);
199    return RESTOOL_SUCCESS;
200}
201
202uint32_t PackageParser::ForceWrite()
203{
204    forceWrite_ = true;
205    return RESTOOL_SUCCESS;
206}
207
208uint32_t PackageParser::PrintVersion()
209{
210    cout << "Info: Restool version= " << RESTOOL_VERSION << endl;
211    exit(RESTOOL_SUCCESS);
212    return RESTOOL_SUCCESS;
213}
214
215uint32_t PackageParser::AddMoudleNames(const string& argValue)
216{
217    if (!moduleNames_.empty()) {
218        cerr << "Error: -m double module name '" << argValue << "'" << endl;
219        return RESTOOL_ERROR;
220    }
221
222    ResourceUtil::Split(argValue, moduleNames_, ",");
223    for (auto it = moduleNames_.begin(); it != moduleNames_.end(); it++) {
224        auto ret = find_if(moduleNames_.begin(), moduleNames_.end(), [it](auto iter) {return *it == iter;});
225        if (ret != it) {
226            cerr << "Error: double module name '" << *it << "'" << endl;
227            return RESTOOL_ERROR;
228        }
229    }
230    return RESTOOL_SUCCESS;
231}
232
233uint32_t PackageParser::AddConfig(const string& argValue)
234{
235    if (!configPath_.empty()) {
236        cerr << "Error: double config.json " << configPath_ << " vs " << argValue << endl;
237        return RESTOOL_ERROR;
238    }
239
240    configPath_ = argValue;
241    return RESTOOL_SUCCESS;
242}
243
244uint32_t PackageParser::AddStartId(const string& argValue)
245{
246    startId_ = strtoll(argValue.c_str(), nullptr, 16); // 16 is hexadecimal number
247    if ((startId_ >= 0x01000000 && startId_ < 0x06ffffff) || (startId_ >= 0x08000000 && startId_ < 0xffffffff)) {
248        return RESTOOL_SUCCESS;
249    }
250    cerr << "Error: invalid start id " << argValue << endl;
251    return RESTOOL_ERROR;
252}
253
254// -i input directory, add the resource directory
255void PackageParser::AdaptResourcesDirForInput()
256{
257    if (!isFileList_ && !combine_) { // -l and increment compile -i, no need to add resource directory
258        for (auto &path : inputs_) {
259            path = FileEntry::FilePath(path).Append(RESOURCES_DIR).GetPath();
260        }
261    }
262}
263
264uint32_t PackageParser::CheckParam() const
265{
266    if (inputs_.empty() && append_.empty()) {
267        cerr << "Error: input path empty." << endl;
268        return RESTOOL_ERROR;
269    }
270
271    if (output_.empty()) {
272        cerr << "Error: output path empty." << endl;
273        return RESTOOL_ERROR;
274    }
275
276    if (isTtargetConfig_ && !append_.empty()) {
277        cerr << "Error: -x and --target-config cannot be used together." << endl;
278        return RESTOOL_ERROR;
279    }
280
281    if (!append_.empty()) {
282        return RESTOOL_SUCCESS;
283    }
284
285    if (packageName_.empty()) {
286        cerr << "Error: package name empty." << endl;
287        return RESTOOL_ERROR;
288    }
289
290    if (resourceHeaderPaths_.empty()) {
291        cerr << "Error: resource header path empty." << endl;
292        return RESTOOL_ERROR;
293    }
294
295    if (startId_ != 0 && !idDefinedInputPath_.empty()) {
296        cerr << "Error: set -e and --defined-ids cannot be used together." << endl;
297        return RESTOOL_ERROR;
298    }
299
300    return RESTOOL_SUCCESS;
301}
302
303bool PackageParser::IsFileList() const
304{
305    return isFileList_;
306}
307
308uint32_t PackageParser::AddAppend(const string& argValue)
309{
310    string appendPath = ResourceUtil::RealPath(argValue);
311    if (appendPath.empty()) {
312        cout << "Warning: invalid compress '" << argValue << "'" << endl;
313        appendPath = argValue;
314    }
315    auto ret = find_if(append_.begin(), append_.end(), [appendPath](auto iter) {return appendPath == iter;});
316    if (ret != append_.end()) {
317        cerr << "Error: repeat input '" << argValue << "'" << endl;
318        return RESTOOL_ERROR;
319    }
320    if (!IsAscii(appendPath)) {
321        return RESTOOL_ERROR;
322    }
323    append_.push_back(appendPath);
324    return RESTOOL_SUCCESS;
325}
326
327const vector<string> &PackageParser::GetAppend() const
328{
329    return append_;
330}
331
332uint32_t PackageParser::SetCombine()
333{
334    combine_ = true;
335    return RESTOOL_SUCCESS;
336}
337
338bool PackageParser::GetCombine() const
339{
340    return combine_;
341}
342
343uint32_t PackageParser::AddDependEntry(const string& argValue)
344{
345    dependEntry_ = argValue;
346    return RESTOOL_SUCCESS;
347}
348
349uint32_t PackageParser::ShowHelp() const
350{
351    auto &parser = CmdParser<PackageParser>::GetInstance();
352    parser.ShowUseage();
353    exit(RESTOOL_SUCCESS);
354    return RESTOOL_SUCCESS;
355}
356
357uint32_t PackageParser::SetIdDefinedOutput(const string& argValue)
358{
359    idDefinedOutput_ = argValue;
360    return RESTOOL_SUCCESS;
361}
362
363const string &PackageParser::GetIdDefinedOutput() const
364{
365    return idDefinedOutput_;
366}
367
368uint32_t PackageParser::SetIdDefinedInputPath(const string& argValue)
369{
370    idDefinedInputPath_ = argValue;
371    return RESTOOL_SUCCESS;
372}
373
374const string &PackageParser::GetIdDefinedInputPath() const
375{
376    return idDefinedInputPath_;
377}
378
379uint32_t PackageParser::IconCheck()
380{
381    isIconCheck_ = true;
382    return RESTOOL_SUCCESS;
383}
384
385bool PackageParser::GetIconCheck() const
386{
387    return isIconCheck_;
388}
389
390uint32_t PackageParser::ParseTargetConfig(const string& argValue)
391{
392    if (isTtargetConfig_) {
393        cerr << "Error: repeat input '--target-config'" << endl;
394        return RESTOOL_ERROR;
395    }
396    if (!SelectCompileParse::ParseTargetConfig(argValue, targetConfig_)) {
397        cerr << "Error: '" << argValue << "' is not valid parameter." << endl;
398        return RESTOOL_ERROR;
399    }
400    isTtargetConfig_ = true;
401    return RESTOOL_SUCCESS;
402}
403
404const TargetConfig &PackageParser::GetTargetConfigValues() const
405{
406    return targetConfig_;
407}
408
409bool PackageParser::IsTargetConfig() const
410{
411    return isTtargetConfig_;
412}
413
414bool PackageParser::IsAscii(const string& argValue) const
415{
416#ifdef __WIN32
417    auto result = find_if(argValue.begin(), argValue.end(), [](auto iter) {
418        if ((iter & 0x80) != 0) {
419            return true;
420        }
421        return false;
422    });
423    if (result != argValue.end()) {
424        cerr << "Error: '" << argValue << "' must be ASCII" << endl;
425        return false;
426    }
427#endif
428    return true;
429}
430
431uint32_t PackageParser::AddCompressionPath(const std::string& argValue)
432{
433    if (!compressionPath_.empty()) {
434        cerr << "Error: double opt-compression.json " << compressionPath_ << " vs " << argValue << endl;
435        return RESTOOL_ERROR;
436    }
437    compressionPath_ = argValue;
438    return RESTOOL_SUCCESS;
439}
440
441const std::string &PackageParser::GetCompressionPath() const
442{
443    return compressionPath_;
444}
445
446void PackageParser::InitCommand()
447{
448    using namespace placeholders;
449    handles_.emplace(Option::INPUTPATH, bind(&PackageParser::AddInput, this, _1));
450    handles_.emplace(Option::PACKAGENAME, bind(&PackageParser::AddPackageName, this, _1));
451    handles_.emplace(Option::OUTPUTPATH, bind(&PackageParser::AddOutput, this, _1));
452    handles_.emplace(Option::RESHEADER, bind(&PackageParser::AddResourceHeader, this, _1));
453    handles_.emplace(Option::FORCEWRITE, [this](const string &) -> uint32_t { return ForceWrite(); });
454    handles_.emplace(Option::VERSION, [this](const string &) -> uint32_t { return PrintVersion(); });
455    handles_.emplace(Option::MODULES, bind(&PackageParser::AddMoudleNames, this, _1));
456    handles_.emplace(Option::JSON, bind(&PackageParser::AddConfig, this,  _1));
457    handles_.emplace(Option::STARTID, bind(&PackageParser::AddStartId, this, _1));
458    handles_.emplace(Option::APPEND, bind(&PackageParser::AddAppend, this, _1));
459    handles_.emplace(Option::COMBINE, [this](const string &) -> uint32_t { return SetCombine(); });
460    handles_.emplace(Option::DEPENDENTRY, bind(&PackageParser::AddDependEntry, this, _1));
461    handles_.emplace(Option::HELP, [this](const string &) -> uint32_t { return ShowHelp(); });
462    handles_.emplace(Option::IDS, bind(&PackageParser::SetIdDefinedOutput, this, _1));
463    handles_.emplace(Option::DEFINED_IDS, bind(&PackageParser::SetIdDefinedInputPath, this, _1));
464    handles_.emplace(Option::ICON_CHECK, [this](const string &) -> uint32_t { return IconCheck(); });
465    handles_.emplace(Option::TARGET_CONFIG, bind(&PackageParser::ParseTargetConfig, this, _1));
466    handles_.emplace(Option::DEFINED_SYSIDS, bind(&PackageParser::AddSysIdDefined, this, _1));
467    handles_.emplace(Option::COMPRESSED_CONFIG, bind(&PackageParser::AddCompressionPath, this, _1));
468}
469
470uint32_t PackageParser::HandleProcess(int c, const string& argValue)
471{
472    auto handler = handles_.find(c);
473    if (handler == handles_.end()) {
474        cerr << "Error: unsupport " << c << endl;
475        return RESTOOL_ERROR;
476    }
477    return handler->second(argValue);
478}
479
480uint32_t PackageParser::ParseFileList(const string& fileListPath)
481{
482    isFileList_ = true;
483    ResConfigParser resConfigParser;
484    if (resConfigParser.Init(fileListPath, [this](int c, const string &argValue) -> uint32_t {
485        return HandleProcess(c, argValue);
486    }) != RESTOOL_SUCCESS) {
487        return RESTOOL_ERROR;
488    }
489    return RESTOOL_SUCCESS;
490}
491
492uint32_t PackageParser::CheckError(int argc, char *argv[], int c, int optIndex)
493{
494    if (optIndex != -1) {
495        if ((optarg == nullptr && (optind - 1 < 0 || optind - 1 >= argc)) ||
496            (optarg != nullptr && (optind - 2 < 0 || optind - 2 >= argc))) { // 1 or 2 menas optind offset value
497            return RESTOOL_ERROR;
498        }
499        string curOpt = (optarg == nullptr) ? argv[optind - 1] : argv[optind - 2];
500        if (curOpt != ("--" + string(CMD_OPTS[optIndex].name))) {
501            cerr << "Error: unknown option " << curOpt << endl;
502            return RESTOOL_ERROR;
503        }
504    }
505    if (c == Option::UNKNOWN) {
506        if (optopt == 0 && (optind - 1 < 0 || optind - 1 >= argc)) {
507            return RESTOOL_ERROR;
508        }
509        string optUnknown = (optopt == 0) ? argv[optind - 1] : ("-" + string(1, optopt));
510        cerr << "Error: unknown option " << optUnknown << endl;
511        return RESTOOL_ERROR;
512    }
513    if (c == Option::NO_ARGUMENT) {
514        if (optind - 1 < 0 || optind - 1 >= argc) {
515            return RESTOOL_ERROR;
516        }
517        if (IsLongOpt(argc, argv)) {
518            cerr << "Error: option " << argv[optind - 1] << " must have argument" << endl;
519        } else {
520            cerr << "Error: unknown option " << argv[optind - 1] << endl;
521        }
522        return RESTOOL_ERROR;
523    }
524    return RESTOOL_SUCCESS;
525}
526
527uint32_t PackageParser::ParseCommand(int argc, char *argv[])
528{
529    restoolPath_ = string(argv[0]);
530    while (true) {
531        int optIndex = -1;
532        int c = getopt_long(argc, argv, CMD_PARAMS.c_str(), CMD_OPTS, &optIndex);
533        if (CheckError(argc, argv, c, optIndex) != RESTOOL_SUCCESS) {
534            return RESTOOL_ERROR;
535        }
536        if (c == Option::END) {
537            if (argc == optind) {
538                break;
539            }
540            string errmsg = "Error: invalid arguments : ";
541            for (int i = optind; i < argc; i++) {
542                errmsg.append(argv[i]).append(" ");
543            }
544            cerr << errmsg << endl;
545            return RESTOOL_ERROR;
546        }
547
548        string argValue = (optarg != nullptr) ? optarg : "";
549        if (c == Option::FILELIST) {
550            return ParseFileList(argValue);
551        }
552        if (HandleProcess(c, argValue) != RESTOOL_SUCCESS) {
553            return RESTOOL_ERROR;
554        }
555    }
556    return RESTOOL_SUCCESS;
557}
558
559bool PackageParser::IsLongOpt(int argc, char *argv[]) const
560{
561    if (optind - 1 < 0 || optind - 1 >= argc) {
562        return false;
563    }
564    for (auto iter : CMD_OPTS) {
565        if (optopt == iter.val && argv[optind - 1] == ("--" + string(iter.name))) {
566            return true;
567        }
568    }
569    return false;
570}
571}
572}
573}
574