1use crate::prelude::*;
6
7pub static UPSTREAM_MAIN_BRANCH: &str = "master";
8
9pub struct Local {
10 g2: git2::Repository,
11 remote_head_refname: String,
12 cfg: Arc<Config>,
13}
14
15#[derive(Copy,Clone,Debug,Hash,Eq,PartialEq,Ord,PartialOrd)]
16#[derive(EnumString)]
17#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
18pub enum CveListState {
19 #[strum(disabled)] Pending,
20 Reserved,
21 Public,
22}
23
24#[ext(pub)]
25impl XsaNum {
26 fn cvelist_branch_name(self) -> String { format!("xsa-{}", self) }
27
28 fn title_with_cves(self, cves: &mut dyn Iterator<Item=&CveName>) -> String {
29 let mut m = String::new();
30 write!(m, "XSA-{}", self).unwrap();
31 for cve in cves { write!(m, " {}", cve).unwrap(); }
32 m
33 }
34}
35
36impl Local {
37 #[throws(AE)]
38 pub fn new(cfg: &Arc<Config>, cache: &mut cache::Cache) -> Self {
39
40 let g2 = git2::Repository::open(&cfg.cvelist_git)
41 .context("open cvelist.git repo")?;
42
43 let remote_head_refname = format!(
44 "refs/remotes/{}/{}",
45 cfg.cvelist_remote_upstream,
46 UPSTREAM_MAIN_BRANCH
47 );
48
49 let mut repo = Local { g2, remote_head_refname, cfg: cfg.clone() };
50
51 let mut fetch_1 = |kind, remote_name: &'_ str, branch_pat: &'_ _| {
52 let did = cache.run_git_fetch_pull(
53 kind, &mut repo,
54 &["fetch",
55 "--prune",
56 "--no-tags"],
57 remote_name,
58 &[branch_pat],
59 &[],
60 )
61 .with_context(|| format!("remote={} branches={}",
62 remote_name, branch_pat))?;
63 cache.did_refresh(did);
64 Ok::<_,AE>(())
65 };
66
67 fetch_1(RefreshKind::CveListUpstream,
68 &cfg.cvelist_remote_upstream,
69 UPSTREAM_MAIN_BRANCH)?;
70
71 fetch_1(RefreshKind::CveListXenSec,
72 &cfg.cvelist_remote_xensec,
73 "*")?;
74
75 repo
76 }
77
78 pub fn cve_path(&self, cve: &CveName) -> String {
79 let mut pieces = cve.as_str().split('-').skip(1);
80 let year: u32 = pieces.next().unwrap().parse().unwrap();
81 let num : u32 = pieces.next().unwrap().parse().unwrap();
82 format!("{}/{}xxx/{}.json", year, num/1000, cve)
83 }
84
85 #[throws(AE)]
86 pub fn cve_data_raw(&self, refspec: RefSpec, cve: &CveName)
87 -> Option<serde_json::Value>
88 {
89 let path = self.cve_path(cve);
90 (||{
91 let data = match self.file(refspec, &path).context("get file")? {
92 Some(x) => x,
93 None => return Ok(None),
94 };
95 let data = serde_json::from_str(&data).context("parse as JSON")?;
96 Ok::<_,AE>(Some(data))
97 })().with_context(|| format!("get cvelist data for {} from {:?}",
98 &cve, path))?
99 }
100
101 #[throws(AE)]
102 pub fn cve_state(&self, refspec: RefSpec, cve: &CveName)
103 -> Option<CveListState>
104 {
105 let data = match self.cve_data_raw(refspec, cve)? {
106 None => return None,
107 Some(x) => x,
108 };
109
110 let cvelist_state = data
111 .as_object() .ok_or_else(|| anyhow!("root not object"))?
112 .get("CVE_data_meta") .ok_or_else(|| anyhow!("no CVE_data_meta"))?;
113
114 #[derive(Deserialize,Debug)]
115 #[allow(non_snake_case)]
116 struct M {
117 ID: String,
118 ASSIGNER: String,
119 STATE: Option<String>,
120 }
121
122 let m: M = serde_json::from_value(cvelist_state.clone())
123 .context("deserialize CVE_data_meta")?;
124
125 if m.ID != cve.as_str() {
126 throw!(anyhow!("CVE_data_meta ID is wrong! {:?}", &m));
127 }
128
129 if let Some(state) = m.STATE {
130 Some(state.parse().context("parse")?)
131 } else if &m.ASSIGNER == &self.cfg.committer_email {
132 Some(CveListState::Pending)
133 } else {
134 throw!(anyhow!(
135 "inexplicable CVE_data_meta (no STATE, ASSIGNER is {:?} not us {:?}: \
136 {:?}", &m.ASSIGNER, &self.cfg.committer_email, &m
137 ))
138 }
139 }
140
141 pub fn xsa_mr_branch_refname(&self, xsa: XsaNum) -> String {
142 format!("refs/remotes/{}/{}",
143 &self.cfg.cvelist_remote_xensec,
144 &xsa.cvelist_branch_name())
145 }
146
147 #[throws(AE)]
148 pub fn remote_url(&self, remote_name: &str) -> String {
149 (||{
150 let remote = self.g2.find_remote(remote_name).context("find remote")?;
151 let url = remote.url().ok_or_else(|| anyhow!("invalid UTF-8 in url"))?;
152 Ok::<_,AE>(url.to_owned())
153 })()
154 .with_context(|| format!("remote {:?}", remote_name))?
155 }
156
157 #[throws(AE)]
158 pub fn make_xsajson_commit(&self, xsa_git: &xsagit::Repo,
159 xsa: XsaNum, vsn: XsaVsn,
160 jpaths: &BTreeMap<CveName,String>)
161 -> git2::Oid
162 {
163 let base_commitid = self.g2()
164 .refname_to_id(&self.remote_head_refname)
165 .context("look up base commitid")?;
166 let base_commit = self.g2().find_commit(base_commitid)
167 .context("find base commit")?;
168 let base_tree = base_commit.tree()
169 .context("get ase tree")?;
170 let mut builder = git2::build::TreeUpdateBuilder::new();
171
172 for (cve,jpath) in jpaths {
173 let json_blob = self.g2()
174 .blob_path(jpath.as_ref())
175 .with_context(|| jpath.to_string())
176 .context("construct and write json blob")?;
177
178 builder.upsert(
179 self.cve_path(cve),
180 json_blob,
181 git2::FileMode::Blob,
182 );
183 }
184
185 let new_tree = builder.create_updated(self.g2(), &base_tree)
186 .context("build new tree")?;
187 let new_tree = self.g2().find_tree(new_tree).context("find new tree")?;
188
189 let signature = git2::Signature::now(
190 &self.cfg.committer_name,
191 &self.cfg.committer_email,
192 ).context("make signature line")?;
193
194 let xsagit_head = xsa_git.head_commitid()?;
195
196 let message = (||{
197 let mut m = xsa.title_with_cves(&mut jpaths.keys());
198 write!(m, "\n\n")?;
199 writeln!(m, "Xensec source data: xsa.git#xsa-{}-v{}", xsa, vsn)?;
200 writeln!(m, "Xensec source infra: xsa.git#{}", xsagit_head)?;
201 Ok::<_,fmt::Error>(m)
202 })().unwrap();
203
204 let new_commit = self.g2().commit(
205 None, &signature, &signature, &message,
206 &new_tree, &[&base_commit]
207 ).context("make new commit")?;
208
209 new_commit
210 }
211}
212
213impl githelp::Repo for Local {
214 fn g2(&self) -> &git2::Repository { &self.g2 }
215 fn cfg(&self) -> &Config { &self.cfg }
216 fn dir(&self) -> &str { &self.cfg.cvelist_git }
217 fn def_refname(&self) -> &str { &self.remote_head_refname }
218}