Autopsy  4.11.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
LocalDiskPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.casemodule;
20 
21 import java.io.File;
22 import java.nio.file.Paths;
23 import java.util.Calendar;
24 import java.util.List;
25 import java.util.logging.Level;
26 import javax.swing.JFileChooser;
27 import javax.swing.JPanel;
28 import org.openide.util.NbBundle;
36 
41 @NbBundle.Messages({
42  "LocalDiskPanel.errorMessage.noOpenCaseTitle=No open case available",
43  "LocalDiskPanel.errorMessage.noOpenCaseBody=LocalDiskPanel listener couldn't get the open case.",
44  "LocalDiskPanel.imageWriterError.directoryNotExist=Error - directory does not exist",
45  "LocalDiskPanel.imageWriterError.emptyPath=Error - enter path for VHD",
46  "LocalDiskPanel.imageWriterError.isDirectory=Error - VHD path is a directory",
47  "LocalDiskPanel.imageWriterError.fileExists=Error - VHD path already exists",
48  "LocalDiskPanel.moduleErrorMessage.title=Module Error",
49  "LocalDiskPanel.moduleErrorMessage.body=A module caused an error listening to LocalDiskPanel updates. See log to determine which module. Some data could be incomplete.",
50  "LocalDiskPanel.localDiskMessage.unspecified=Unspecified"
51 })
52 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
53 final class LocalDiskPanel extends JPanel {
54 
55  private static final Logger logger = Logger.getLogger(LocalDiskPanel.class.getName());
56  private static final String[] SECTOR_SIZE_CHOICES = {"Auto Detect", "512", "1024", "2048", "4096"};
57  private static LocalDiskPanel instance;
58  private static final long serialVersionUID = 1L;
59  private LocalDisk localDisk;
60  private boolean enableNext = false;
61  private final JFileChooser fc = new JFileChooser();
62 
66  LocalDiskPanel() {
67  initComponents();
68  customInit();
69  createTimeZoneList();
70  createSectorSizeList();
71  }
72 
76  static synchronized LocalDiskPanel getDefault() {
77  if (instance == null) {
78  instance = new LocalDiskPanel();
79  }
80  return instance;
81  }
82 
83  @SuppressWarnings("unchecked")
84  private void customInit() {
85  imageWriterErrorLabel.setVisible(false);
86  imageWriterErrorLabel.setText("");
87  if (!PlatformUtil.isWindowsOS()) {
88  copyImageCheckbox.setSelected(false);
89  copyImageCheckbox.setEnabled(false);
90  }
91  pathTextField.setEnabled(copyImageCheckbox.isSelected());
92  browseButton.setEnabled(copyImageCheckbox.isSelected());
93  changeDatabasePathCheckbox.setEnabled(copyImageCheckbox.isSelected());
94  }
95 
101  @SuppressWarnings("unchecked")
102  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
103  private void initComponents() {
104 
105  timeZoneLabel = new javax.swing.JLabel();
106  timeZoneComboBox = new javax.swing.JComboBox<>();
107  noFatOrphansCheckbox = new javax.swing.JCheckBox();
108  descLabel = new javax.swing.JLabel();
109  copyImageCheckbox = new javax.swing.JCheckBox();
110  pathTextField = new javax.swing.JTextField();
111  browseButton = new javax.swing.JButton();
112  jLabel1 = new javax.swing.JLabel();
113  imageWriterErrorLabel = new javax.swing.JLabel();
114  changeDatabasePathCheckbox = new javax.swing.JCheckBox();
115  sectorSizeLabel = new javax.swing.JLabel();
116  sectorSizeComboBox = new javax.swing.JComboBox<>();
117  selectDiskButton = new javax.swing.JButton();
118  localDiskLabel = new javax.swing.JLabel();
119  localDiskNameTextField = new javax.swing.JTextField();
120 
121  setMinimumSize(new java.awt.Dimension(0, 420));
122  setPreferredSize(new java.awt.Dimension(485, 410));
123 
124  timeZoneLabel.setFont(timeZoneLabel.getFont().deriveFont(timeZoneLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
125  org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.timeZoneLabel.text")); // NOI18N
126 
127  timeZoneComboBox.setFont(timeZoneComboBox.getFont().deriveFont(timeZoneComboBox.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
128  timeZoneComboBox.setMaximumRowCount(30);
129 
130  noFatOrphansCheckbox.setFont(noFatOrphansCheckbox.getFont().deriveFont(noFatOrphansCheckbox.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
131  org.openide.awt.Mnemonics.setLocalizedText(noFatOrphansCheckbox, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.noFatOrphansCheckbox.text")); // NOI18N
132  noFatOrphansCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.noFatOrphansCheckbox.toolTipText")); // NOI18N
133 
134  descLabel.setFont(descLabel.getFont().deriveFont(descLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
135  org.openide.awt.Mnemonics.setLocalizedText(descLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.descLabel.text")); // NOI18N
136 
137  org.openide.awt.Mnemonics.setLocalizedText(copyImageCheckbox, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.copyImageCheckbox.text")); // NOI18N
138  copyImageCheckbox.addActionListener(new java.awt.event.ActionListener() {
139  public void actionPerformed(java.awt.event.ActionEvent evt) {
140  copyImageCheckboxActionPerformed(evt);
141  }
142  });
143 
144  pathTextField.setText(org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.pathTextField.text")); // NOI18N
145  pathTextField.addKeyListener(new java.awt.event.KeyAdapter() {
146  public void keyReleased(java.awt.event.KeyEvent evt) {
147  pathTextFieldKeyReleased(evt);
148  }
149  });
150 
151  org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.browseButton.text")); // NOI18N
152  browseButton.addActionListener(new java.awt.event.ActionListener() {
153  public void actionPerformed(java.awt.event.ActionEvent evt) {
154  browseButtonActionPerformed(evt);
155  }
156  });
157 
158  org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.jLabel1.text")); // NOI18N
159 
160  imageWriterErrorLabel.setFont(imageWriterErrorLabel.getFont().deriveFont(imageWriterErrorLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
161  imageWriterErrorLabel.setForeground(new java.awt.Color(255, 0, 0));
162  org.openide.awt.Mnemonics.setLocalizedText(imageWriterErrorLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.imageWriterErrorLabel.text")); // NOI18N
163 
164  org.openide.awt.Mnemonics.setLocalizedText(changeDatabasePathCheckbox, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.changeDatabasePathCheckbox.text")); // NOI18N
165 
166  org.openide.awt.Mnemonics.setLocalizedText(sectorSizeLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.sectorSizeLabel.text")); // NOI18N
167 
168  org.openide.awt.Mnemonics.setLocalizedText(selectDiskButton, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.selectDiskButton.text")); // NOI18N
169  selectDiskButton.addActionListener(new java.awt.event.ActionListener() {
170  public void actionPerformed(java.awt.event.ActionEvent evt) {
171  selectDiskButtonActionPerformed(evt);
172  }
173  });
174 
175  org.openide.awt.Mnemonics.setLocalizedText(localDiskLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.localDiskLabel.text")); // NOI18N
176 
177  localDiskNameTextField.setEditable(false);
178 
179  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
180  this.setLayout(layout);
181  layout.setHorizontalGroup(
182  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
183  .addGroup(layout.createSequentialGroup()
184  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
185  .addGroup(layout.createSequentialGroup()
186  .addComponent(localDiskLabel)
187  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
188  .addComponent(localDiskNameTextField)
189  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
190  .addComponent(selectDiskButton))
191  .addGroup(layout.createSequentialGroup()
192  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
193  .addGroup(layout.createSequentialGroup()
194  .addComponent(timeZoneLabel)
195  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
196  .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 275, javax.swing.GroupLayout.PREFERRED_SIZE))
197  .addComponent(noFatOrphansCheckbox)
198  .addComponent(copyImageCheckbox)
199  .addGroup(layout.createSequentialGroup()
200  .addGap(21, 21, 21)
201  .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 342, javax.swing.GroupLayout.PREFERRED_SIZE)
202  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
203  .addComponent(browseButton))
204  .addGroup(layout.createSequentialGroup()
205  .addGap(2, 2, 2)
206  .addComponent(sectorSizeLabel)
207  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
208  .addComponent(sectorSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
209  .addGroup(layout.createSequentialGroup()
210  .addGap(21, 21, 21)
211  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
212  .addComponent(descLabel)
213  .addComponent(changeDatabasePathCheckbox)
214  .addGroup(layout.createSequentialGroup()
215  .addGap(21, 21, 21)
216  .addComponent(jLabel1))
217  .addComponent(imageWriterErrorLabel))))
218  .addGap(0, 39, Short.MAX_VALUE)))
219  .addContainerGap())
220  );
221  layout.setVerticalGroup(
222  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
223  .addGroup(layout.createSequentialGroup()
224  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
225  .addComponent(localDiskLabel)
226  .addComponent(selectDiskButton)
227  .addComponent(localDiskNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
228  .addGap(18, 18, 18)
229  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
230  .addComponent(timeZoneLabel)
231  .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
232  .addGap(18, 18, 18)
233  .addComponent(noFatOrphansCheckbox)
234  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
235  .addComponent(descLabel)
236  .addGap(18, 18, 18)
237  .addComponent(copyImageCheckbox)
238  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
239  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
240  .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
241  .addComponent(browseButton))
242  .addGap(13, 13, 13)
243  .addComponent(changeDatabasePathCheckbox)
244  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
245  .addComponent(jLabel1)
246  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
247  .addComponent(imageWriterErrorLabel)
248  .addGap(12, 12, 12)
249  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
250  .addComponent(sectorSizeLabel)
251  .addComponent(sectorSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
252  .addContainerGap(127, Short.MAX_VALUE))
253  );
254  }// </editor-fold>//GEN-END:initComponents
255 
256  private void copyImageCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_copyImageCheckboxActionPerformed
257  pathTextField.setEnabled(copyImageCheckbox.isSelected());
258  browseButton.setEnabled(copyImageCheckbox.isSelected());
259  changeDatabasePathCheckbox.setEnabled(copyImageCheckbox.isSelected());
260  changeDatabasePathCheckbox.setSelected(copyImageCheckbox.isSelected());
261  fireUpdateEvent();
262  }//GEN-LAST:event_copyImageCheckboxActionPerformed
263 
264  private void pathTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_pathTextFieldKeyReleased
265  fireUpdateEvent();
266  }//GEN-LAST:event_pathTextFieldKeyReleased
267 
268  private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
269  String oldText = pathTextField.getText();
270  // set the current directory of the FileChooser if the ImagePath Field is valid
271  File currentFile = new File(oldText);
272  if ((currentFile.getParentFile() != null) && (currentFile.getParentFile().exists())) {
273  fc.setSelectedFile(currentFile);
274  }
275 
276  int retval = fc.showOpenDialog(this);
277  if (retval == JFileChooser.APPROVE_OPTION) {
278  String path = fc.getSelectedFile().getPath();
279  pathTextField.setText(path);
280  }
281  fireUpdateEvent();
282  }//GEN-LAST:event_browseButtonActionPerformed
283 
284  private void selectDiskButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectDiskButtonActionPerformed
285  LocalDiskSelectionDialog selectionDialog = new LocalDiskSelectionDialog();
286  selectionDialog.display();
287  LocalDisk selectedLocalDisk = selectionDialog.getLocalDiskSelection();
288  if (selectedLocalDisk != null) {
289  localDisk = selectedLocalDisk;
290  localDiskNameTextField.setText(selectedLocalDisk.getName());
291 
292  enableNext = true;
293  try {
294  setPotentialImageWriterPath(localDisk);
295  firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true);
296  } catch (NoCurrentCaseException ex) {
297  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
298  MessageNotifyUtil.Notify.show(Bundle.LocalDiskPanel_errorMessage_noOpenCaseTitle(),
299  Bundle.LocalDiskPanel_errorMessage_noOpenCaseBody(),
300  MessageNotifyUtil.MessageType.ERROR);
301  } catch (Exception ex) {
302  logger.log(Level.SEVERE, "LocalDiskPanel listener threw exception", ex); //NON-NLS
303  MessageNotifyUtil.Notify.show(Bundle.LocalDiskPanel_moduleErrorMessage_title(),
304  Bundle.LocalDiskPanel_moduleErrorMessage_body(),
305  MessageNotifyUtil.MessageType.ERROR);
306  }
307  }
308  }//GEN-LAST:event_selectDiskButtonActionPerformed
309 
310  // Variables declaration - do not modify//GEN-BEGIN:variables
311  private javax.swing.JButton browseButton;
312  private javax.swing.JCheckBox changeDatabasePathCheckbox;
313  private javax.swing.JCheckBox copyImageCheckbox;
314  private javax.swing.JLabel descLabel;
315  private javax.swing.JLabel imageWriterErrorLabel;
316  private javax.swing.JLabel jLabel1;
317  private javax.swing.JLabel localDiskLabel;
318  private javax.swing.JTextField localDiskNameTextField;
319  private javax.swing.JCheckBox noFatOrphansCheckbox;
320  private javax.swing.JTextField pathTextField;
321  private javax.swing.JComboBox<String> sectorSizeComboBox;
322  private javax.swing.JLabel sectorSizeLabel;
323  private javax.swing.JButton selectDiskButton;
324  private javax.swing.JComboBox<String> timeZoneComboBox;
325  private javax.swing.JLabel timeZoneLabel;
326  // End of variables declaration//GEN-END:variables
327 
328  private void fireUpdateEvent() {
329  try {
330  firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true);
331  } catch (Exception e) {
332  logger.log(Level.SEVERE, "LocalDiskPanel listener threw exception", e); //NON-NLS
333  MessageNotifyUtil.Notify.show(Bundle.LocalDiskPanel_moduleErrorMessage_title(),
334  Bundle.LocalDiskPanel_moduleErrorMessage_body(),
335  MessageNotifyUtil.MessageType.ERROR);
336  }
337  }
338 
342  void resetLocalDiskSelection() {
343  localDisk = null;
344  localDiskNameTextField.setText(Bundle.LocalDiskPanel_localDiskMessage_unspecified());
345  enableNext = false;
346  }
347 
353  String getContentPath() {
354  if (localDisk != null) {
355  return localDisk.getPath();
356  } else {
357  return "";
358  }
359  }
360 
366  int getSectorSize() {
367  int sectorSizeSelectionIndex = sectorSizeComboBox.getSelectedIndex();
368 
369  if (sectorSizeSelectionIndex == 0) {
370  return 0;
371  }
372 
373  return Integer.valueOf((String) sectorSizeComboBox.getSelectedItem());
374  }
375 
376  String getTimeZone() {
377  String tz = timeZoneComboBox.getSelectedItem().toString();
378  return tz.substring(tz.indexOf(")") + 2).trim();
379 
380  }
381 
382  boolean getNoFatOrphans() {
383  return noFatOrphansCheckbox.isSelected();
384  }
385 
386  private static String getDefaultImageWriterFolder() throws NoCurrentCaseException {
387  return Paths.get(Case.getCurrentCaseThrows().getModuleDirectory(), "Image Writer").toString();
388  }
389 
390  private void setPotentialImageWriterPath(LocalDisk disk) throws NoCurrentCaseException {
391 
392  File subDirectory = Paths.get(getDefaultImageWriterFolder()).toFile();
393  if (!subDirectory.exists()) {
394  subDirectory.mkdirs();
395  }
396 
397  String path = disk.getName();
398 
399  // Remove any character that isn't alphanumeric, a space, parent, or underscore.
400  // If the name ends up empty or starting with a space, prepend "localDisk"
401  path = path.replaceAll("[^0-9A-Za-z _()]", ""); // NON-NLS
402  if (path.isEmpty() || path.startsWith(" ")) {
403  path = "localDisk" + path;
404  }
405 
406  path += " " + System.currentTimeMillis();
407  path += ".vhd";
408  pathTextField.setText(Paths.get(getDefaultImageWriterFolder(), path).toString());
409  }
410 
411  private boolean imageWriterPathIsValid() {
412  if ((!copyImageCheckbox.isSelected()) || localDisk == null) {
413  imageWriterErrorLabel.setVisible(false);
414  imageWriterErrorLabel.setText("");
415  return true;
416  }
417 
418  if (pathTextField.getText().isEmpty()) {
419  imageWriterErrorLabel.setVisible(true);
420  imageWriterErrorLabel.setText(Bundle.LocalDiskPanel_imageWriterError_emptyPath());
421  return false;
422  }
423 
424  File f = new File(pathTextField.getText());
425  if (((f.getParentFile() != null) && (!f.getParentFile().exists()))
426  || (f.getParentFile() == null)) {
427  imageWriterErrorLabel.setVisible(true);
428  imageWriterErrorLabel.setText(Bundle.LocalDiskPanel_imageWriterError_directoryNotExist());
429  return false;
430  }
431  if (f.isDirectory()) {
432  imageWriterErrorLabel.setVisible(true);
433  imageWriterErrorLabel.setText(Bundle.LocalDiskPanel_imageWriterError_isDirectory());
434  return false;
435  }
436  if (f.exists()) {
437  imageWriterErrorLabel.setVisible(true);
438  imageWriterErrorLabel.setText(Bundle.LocalDiskPanel_imageWriterError_fileExists());
439  return false;
440  }
441  imageWriterErrorLabel.setVisible(false);
442  imageWriterErrorLabel.setText("");
443  return true;
444  }
445 
446  boolean getImageWriterEnabled() {
447  return copyImageCheckbox.isSelected();
448  }
449 
450  ImageWriterSettings getImageWriterSettings() {
451  return new ImageWriterSettings(pathTextField.getText(), changeDatabasePathCheckbox.isSelected());
452  }
453 
460  boolean validatePanel() {
461  if (!imageWriterPathIsValid()) {
462  return false;
463  }
464  return enableNext;
465  }
466 
471  public void createTimeZoneList() {
472  List<String> timeZoneList = TimeZoneUtils.createTimeZoneList();
473  for (String timeZone : timeZoneList) {
474  timeZoneComboBox.addItem(timeZone);
475  }
476 
477  // set the selected timezone
478  timeZoneComboBox.setSelectedItem(TimeZoneUtils.createTimeZoneString(Calendar.getInstance().getTimeZone()));
479  }
480 
485  private void createSectorSizeList() {
486  for (String choice : SECTOR_SIZE_CHOICES) {
487  sectorSizeComboBox.addItem(choice);
488  }
489  sectorSizeComboBox.setSelectedIndex(0);
490  }
491 }

Copyright © 2012-2018 Basis Technology. Generated on: Fri Jun 21 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.