• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GothenburgBitFactory / taskwarrior / 11418135454

19 Oct 2024 02:00PM UTC coverage: 84.87% (+0.6%) from 84.223%
11418135454

Pull #3655

github

web-flow
Add SECURITY.md
Pull Request #3655: Add SECURITY.md

19004 of 22392 relevant lines covered (84.87%)

23086.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.66
/src/commands/CmdContext.cpp
1
////////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
4
//
5
// Permission is hereby granted, free of charge, to any person obtaining a copy
6
// of this software and associated documentation files (the "Software"), to deal
7
// in the Software without restriction, including without limitation the rights
8
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
// copies of the Software, and to permit persons to whom the Software is
10
// furnished to do so, subject to the following conditions:
11
//
12
// The above copyright notice and this permission notice shall be included
13
// in all copies or substantial portions of the Software.
14
//
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
// SOFTWARE.
22
//
23
// https://www.opensource.org/licenses/mit-license.php
24
//
25
////////////////////////////////////////////////////////////////////////////////
26

27
#include <cmake.h>
28
// cmake.h include header must come first
29

30
#include <CmdConfig.h>
31
#include <CmdContext.h>
32
#include <Context.h>
33
#include <Filter.h>
34
#include <Table.h>
35
#include <format.h>
36
#include <main.h>
37
#include <shared.h>
38
#include <util.h>
39

40
#include <algorithm>
41
#include <set>
42
#include <sstream>
43

44
////////////////////////////////////////////////////////////////////////////////
45
CmdContext::CmdContext() {
4,497✔
46
  _keyword = "context";
4,497✔
47
  _usage = "task          context [<name> | <subcommand>]";
4,497✔
48
  _description = "Set and define contexts (default filters / modifications)";
4,497✔
49
  _read_only = true;
4,497✔
50
  _displays_id = false;
4,497✔
51
  _needs_gc = false;
4,497✔
52
  _uses_context = false;
4,497✔
53
  _accepts_filter = false;
4,497✔
54
  _accepts_modifications = false;
4,497✔
55
  _accepts_miscellaneous = true;
4,497✔
56
  _category = Command::Category::context;
4,497✔
57
}
4,497✔
58

59
////////////////////////////////////////////////////////////////////////////////
60
int CmdContext::execute(std::string& output) {
106✔
61
  std::stringstream out;
106✔
62

63
  // Get the non-attribute, non-fancy command line arguments.
64
  auto words = Context::getContext().cli2.getWords();
106✔
65
  if (words.size() > 0) {
106✔
66
    auto subcommand = words[0];
106✔
67

68
    if (subcommand == "define")
106✔
69
      defineContext(words, out);
56✔
70
    else if (subcommand == "delete")
50✔
71
      deleteContext(words, out);
4✔
72
    else if (subcommand == "list")
46✔
73
      listContexts(out);
3✔
74
    else if (subcommand == "none")
43✔
75
      unsetContext(out);
6✔
76
    else if (subcommand == "show")
37✔
77
      showContext(out);
8✔
78
    else if (words.size())
29✔
79
      setContext(words, out);
29✔
80
  } else {
106✔
81
    listContexts(out);
×
82
    out << "Use 'task context none' to unset the current context.\n";
×
83
  }
84

85
  output = out.str();
98✔
86
  return 0;
98✔
87
}
114✔
88

89
////////////////////////////////////////////////////////////////////////////////
90
// Joins all the words in the specified interval <from, to) to one string,
91
// which is then returned.
92
//
93
// If to is specified as 0 (default value), all the remaining words will be joined.
94
//
95
std::string CmdContext::joinWords(const std::vector<std::string>& words, unsigned int from,
55✔
96
                                  unsigned int to /* = 0 */) {
97
  std::string value = "";
55✔
98

99
  if (to == 0) to = words.size();
55✔
100

101
  for (unsigned int i = from; i < to; ++i) {
113✔
102
    if (i > from) value += ' ';
58✔
103

104
    value += words[i];
58✔
105
  }
106

107
  return value;
55✔
108
}
109

110
////////////////////////////////////////////////////////////////////////////////
111
// Validate the context as valid for writing and fail the write context definition
112
// A valid write context:
113
//   - does not contain any operators except AND
114
//   - does not contain tag exclusion
115
//   - does not use modifiers, except for 'equals' and 'is'
116
//
117
// Returns True if the context is a valid write context. If the context is
118
// invalid due to a wrong modifier use, the modifier string will contain the
119
// first invalid modifier.
120
//
121
bool CmdContext::validateWriteContext(const std::vector<A2>& lexedArgs, std::string& reason) {
55✔
122
  for (auto& arg : lexedArgs) {
219✔
123
    if (arg._lextype == Lexer::Type::op)
167✔
124
      if (arg.attribute("raw") == "or") {
1✔
125
        reason = "contains the 'OR' operator";
1✔
126
        return false;
3✔
127
      }
128

129
    if (arg._lextype == Lexer::Type::pair) {
166✔
130
      auto modifier = arg.attribute("modifier");
68✔
131
      if (modifier != "" && modifier != "is" && modifier != "equals") {
34✔
132
        reason = format("contains an attribute modifier '{1}'", arg.attribute("raw"));
1✔
133
        return false;
1✔
134
      }
135
    }
34✔
136

137
    if (arg._lextype == Lexer::Type::tag) {
165✔
138
      if (arg.attribute("sign") == "-") {
21✔
139
        reason = format("contains tag exclusion '{1}'", arg.attribute("raw"));
1✔
140
        return false;
1✔
141
      }
142
    }
143
  }
144

145
  return true;
52✔
146
}
147

148
////////////////////////////////////////////////////////////////////////////////
149
// Returns all user defined contexts.
150
//
151
std::vector<std::string> CmdContext::getContexts() {
34✔
152
  std::set<std::string> contexts;
34✔
153

154
  for (auto& name : Context::getContext().config)
8,476✔
155
    if (name.first.substr(0, 8) == "context.") {
8,442✔
156
      std::string suffix = name.first.substr(8);
99✔
157

158
      if (suffix.find(".") != std::string::npos)
99✔
159
        contexts.insert(suffix.substr(0, suffix.find(".")));
97✔
160
      else
161
        contexts.insert(suffix);
2✔
162
    }
99✔
163

164
  return std::vector<std::string>(contexts.begin(), contexts.end());
68✔
165
}
34✔
166

167
////////////////////////////////////////////////////////////////////////////////
168
// Defines a new user-provided context.
169
//  - The context definition is written into .taskrc as a context.<name> variable.
170
//  - Deletion of the context requires confirmation if rc.confirmation=yes.
171
//
172
// Returns: 0 if the addition of the config variable was successful, 1 otherwise
173
//
174
// Invoked with: task context define <name> <filter>
175
// Example:      task context define home project:Home
176
//
177
void CmdContext::defineContext(const std::vector<std::string>& words, std::stringstream& out) {
56✔
178
  auto config = Context::getContext().config;
56✔
179
  bool confirmation = config.getBoolean("confirmation");
56✔
180

181
  if (words.size() > 2) {
56✔
182
    auto name = "context." + words[1];
55✔
183
    auto value = joinWords(words, 2);
55✔
184

185
    // Make sure nobody creates a context with name 'list', 'none' or 'show'
186
    if (words[1] == "none" or words[1] == "list" or words[1] == "show") {
55✔
187
      throw format("The name '{1}' is reserved and not allowed to use as a context name.",
×
188
                   words[1]);
×
189
    }
190

191
    // Extract MISCELLANEOUS arguments (containing the filter definition) for later analysis
192
    std::vector<A2> lexedArgs = Context::getContext().cli2.getMiscellaneous();
55✔
193

194
    // Check if the value is a proper filter by filtering current pending.data
195
    Filter filter;
55✔
196
    std::vector<Task> filtered;
55✔
197
    auto pending = Context::getContext().tdb2.pending_tasks();
55✔
198

199
    try {
200
      // This result is not used, and is just to check validity.
201
      Context::getContext().cli2.addFilter(value);
55✔
202
      filter.subset(pending, filtered);
55✔
203
    } catch (std::string exception) {
×
204
      throw format("Filter validation failed: {1}", exception);
×
205
    }
206

207
    // Make user explicitly confirm filters that are matching no pending tasks
208
    if (filtered.size() == 0)
55✔
209
      if (confirmation &&
81✔
210
          !confirm(
28✔
211
              format("The filter '{1}' matches 0 pending tasks. Do you wish to continue?", value)))
81✔
212
        throw std::string("Context definition aborted.");
×
213

214
    std::string reason = "";
55✔
215
    bool valid_write_context = CmdContext::validateWriteContext(lexedArgs, reason);
55✔
216

217
    if (!valid_write_context) {
55✔
218
      std::stringstream warning;
3✔
219
      warning
220
          << format("The filter '{1}' is not a valid modification string, because it contains {2}.",
6✔
221
                    value, reason)
222
          << "\nAs such, value for the write context cannot be set (context will not apply on task "
223
             "add / task log).\n\n"
224
          << format(
6✔
225
                 "Please use 'task config context.{1}.write <default mods>' to set default "
226
                 "attribute values for new tasks in this context manually.\n\n",
227
                 words[1]);
6✔
228
      out << colorizeFootnote(warning.str());
3✔
229
    }
3✔
230

231
    // Set context definition config variable
232
    bool read_success = CmdConfig::setConfigVariable(name + ".read", value, confirmation);
55✔
233
    bool write_success = false;
55✔
234

235
    if (valid_write_context)
55✔
236
      write_success = CmdConfig::setConfigVariable(name + ".write", value, confirmation);
52✔
237

238
    // Remove old-school context name, if it exists, assuming the read context was defined
239
    if (read_success)
55✔
240
      if (config.has(name)) {
54✔
241
        CmdConfig::unsetConfigVariable(name, false);
×
242
      }
243

244
    if (!read_success and !write_success)
55✔
245
      throw format("Context '{1}' not defined.", words[1]);
1✔
246
    else if (!read_success)
54✔
247
      out << format("Context '{1}' defined (write only).", words[1]);
×
248
    else if (!write_success)
54✔
249
      out << format("Context '{1}' defined (read only).", words[1]);
27✔
250
    else
251
      out << format("Context '{1}' defined (read, write).", words[1]);
27✔
252

253
    out << format(" Use 'task context {1}' to activate.\n", words[1]);
54✔
254
  } else
60✔
255
    throw std::string("Both context name and its definition must be provided.");
1✔
256
}
56✔
257

258
////////////////////////////////////////////////////////////////////////////////
259
// Deletes the specified context.
260
//  - If the deleted context is currently active, unset it.
261
//  - Deletion of the context requires confirmation if rc.confirmation=yes.
262
//
263
// Returns: 0 if the removal of the config variable was successful, 1 otherwise
264
//
265
// Invoked with: task context delete <name>
266
// Example:      task context delete home
267
//
268
void CmdContext::deleteContext(const std::vector<std::string>& words, std::stringstream& out) {
4✔
269
  if (words.size() > 1) {
4✔
270
    // Delete the specified context
271
    auto name = "context." + words[1];
3✔
272

273
    auto confirmation = Context::getContext().config.getBoolean("confirmation");
3✔
274
    if (confirmation && !confirm(format("Do you want to delete context '{1}'?", words[1])))
3✔
275
      throw format("Context '{1}' not deleted.", words[1]);
×
276

277
    // Delete legacy format and .read / .write flavours
278
    auto rc = CmdConfig::unsetConfigVariable(name, false);
3✔
279
    rc += CmdConfig::unsetConfigVariable(name + ".read", false);
3✔
280
    rc += CmdConfig::unsetConfigVariable(name + ".write", false);
3✔
281

282
    // If the currently set context was deleted, unset it
283
    if (Context::getContext().config.get("context") == words[1])
3✔
284
      CmdConfig::unsetConfigVariable("context", false);
1✔
285

286
    // Output feedback, rc should be even because only 0 (found and removed)
287
    // and 2 (not found) are aceptable return values from unsetConfigVariable
288
    if (rc % 2 != 0)
3✔
289
      throw format("Context '{1}' not deleted.", words[1]);
×
290
    else if (rc == 6)
3✔
291
      throw format("Context '{1}' not found.", words[1]);
1✔
292

293
    out << format("Context '{1}' deleted.\n", words[1]);
2✔
294
  } else
3✔
295
    throw std::string("Context name needs to be specified.");
1✔
296
}
2✔
297

298
////////////////////////////////////////////////////////////////////////////////
299
// Render a list of context names and their definitions.
300
//
301
// Returns: 0 the resulting list is non-empty, 1 otherwise
302
//
303
// Invoked with: task context list
304
// Example:      task context list
305
//
306
void CmdContext::listContexts(std::stringstream& out) {
3✔
307
  auto contexts = getContexts();
3✔
308
  if (contexts.size()) {
3✔
309
    std::sort(contexts.begin(), contexts.end());
2✔
310

311
    Table table;
2✔
312
    table.width(Context::getContext().getWidth());
2✔
313
    table.add("Name");
2✔
314
    table.add("Type");
2✔
315
    table.add("Definition");
2✔
316
    table.add("Active");
2✔
317
    setHeaderUnderline(table);
2✔
318

319
    std::string activeContext = Context::getContext().config.get("context");
4✔
320

321
    for (auto& userContext : contexts) {
5✔
322
      std::string active = "no";
3✔
323
      if (userContext == activeContext) active = "yes";
3✔
324

325
      int row = table.addRow();
3✔
326
      table.set(row, 0, userContext);
3✔
327
      table.set(row, 1, "read");
3✔
328
      table.set(row, 2, Context::getContext().getTaskContext("read", userContext));
3✔
329
      table.set(row, 3, active);
3✔
330

331
      row = table.addRow();
3✔
332
      table.set(row, 0, "");
3✔
333
      table.set(row, 1, "write");
3✔
334
      table.set(row, 2, Context::getContext().getTaskContext("write", userContext));
3✔
335
      table.set(row, 3, active);
3✔
336
    }
3✔
337

338
    out << optionalBlankLine() << table.render() << optionalBlankLine();
2✔
339
  } else
2✔
340
    throw std::string("No contexts defined.");
1✔
341
}
3✔
342

343
////////////////////////////////////////////////////////////////////////////////
344
// Sets the specified context as currently active.
345
//   - If some other context was active, the value of currently active context
346
//     is replaced, not added.
347
//   - Setting of the context does not require confirmation.
348
//
349
// Returns: 0 if the setting of the context was successful, 1 otherwise
350
//
351
// Invoked with: task context <name>
352
// Example:      task context home
353
//
354
void CmdContext::setContext(const std::vector<std::string>& words, std::stringstream& out) {
29✔
355
  auto value = words[0];
29✔
356
  auto contexts = getContexts();
29✔
357

358
  // Check that the specified context is defined
359
  if (std::find(contexts.begin(), contexts.end(), value) == contexts.end())
29✔
360
    throw format("Context '{1}' not found.", value);
2✔
361

362
  // Set the active context.
363
  // Should always succeed, as we do not require confirmation.
364
  bool success = CmdConfig::setConfigVariable("context", value, false);
27✔
365

366
  if (!success) throw format("Context '{1}' not applied.", value);
27✔
367

368
  out << format("Context '{1}' set. Use 'task context none' to remove.\n", value);
27✔
369
}
31✔
370

371
////////////////////////////////////////////////////////////////////////////////
372
// Shows the currently active context.
373
//
374
// Returns: Always returns 0.
375
//
376
// Invoked with: task context show
377
// Example:      task context show
378
//
379
void CmdContext::showContext(std::stringstream& out) {
8✔
380
  auto currentContext = Context::getContext().config.get("context");
16✔
381

382
  if (currentContext == "")
8✔
383
    out << "No context is currently applied.\n";
7✔
384
  else {
385
    out << format(
2✔
386
        "Context '{1}' with \n\n* read filter: '{2}'\n* write filter: '{3}'\n\nis currently "
387
        "applied.\n",
388
        currentContext, Context::getContext().getTaskContext("read", ""),
2✔
389
        Context::getContext().getTaskContext("write", ""));
3✔
390
  }
391
}
8✔
392

393
////////////////////////////////////////////////////////////////////////////////
394
// Unsets the currently active context.
395
//   - Unsetting of the context does not require confirmation.
396
//
397
// Returns: 0 if the unsetting of the context was successful, 1 otherwise (also
398
//          returned if no context is currently active)
399
//
400
// Invoked with: task context none
401
// Example:      task context none
402
//
403
void CmdContext::unsetContext(std::stringstream& out) {
6✔
404
  if (CmdConfig::unsetConfigVariable("context", false)) throw std::string("Context not unset.");
6✔
405

406
  out << "Context unset.\n";
5✔
407
}
5✔
408

409
////////////////////////////////////////////////////////////////////////////////
410
CmdCompletionContext::CmdCompletionContext() {
4,497✔
411
  _keyword = "_context";
4,497✔
412
  _usage = "task          _context";
4,497✔
413
  _description = "Lists all supported contexts, for completion purposes";
4,497✔
414
  _read_only = true;
4,497✔
415
  _displays_id = false;
4,497✔
416
  _needs_gc = false;
4,497✔
417
  _uses_context = false;
4,497✔
418
  _accepts_filter = false;
4,497✔
419
  _accepts_modifications = false;
4,497✔
420
  _accepts_miscellaneous = false;
4,497✔
421
  _category = Command::Category::internal;
4,497✔
422
}
4,497✔
423

424
////////////////////////////////////////////////////////////////////////////////
425
int CmdCompletionContext::execute(std::string& output) {
2✔
426
  for (auto& context : CmdContext::getContexts()) output += context + '\n';
6✔
427

428
  return 0;
2✔
429
}
430

431
////////////////////////////////////////////////////////////////////////////////
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc