通过 Firefox 扩展为 Google Calendar 加密

在 Google Calendar 中存储表示事件名称和描述的加密数据


developerWorks

文档选项


未显示需要 JavaScript 的文档选项


将打印机的版面设置成横向打印模式

打印本页


将此页作为电子邮件发送

将此页作为电子邮件发送


样例代码


英文原文

英文原文

级别: 初级

Nathan Harrington, I/T 专家, IBM

2008 年 8 月 04 日

当今的 Web 应用程序为在线存储、访问和协作提供了许多便利。虽然一些应用程序为用户数据提供了加密,但为数不多。本文提供了添加基本加密支持所需的工具和代码,使您可以在一个最流行的在线日程表上加密用户数据。通过运用 Firefox 扩展和 Gnu Privacy Guard 的巨大灵活性,本文展示如何将加密的事件描述存储到 Google Calendar 应用程序,并且只向拥有解密密匙的用户显示纯文本。

本文从 Elias Torres 的出色的 “Google Calendar Quick Add” 扩展开始,逐步向您介绍如何提取、更改和插入各种组件,为事件进行加密,而不仅仅是使用加密的 TLS 数据通道。按照本文的说明进行操作之后,将会得到下面 图 1 所示的示例,在这里,服务器操作人员看到的是左边的内容,而 Web 浏览者看到的是右边的内容。

图 1. 加密的 Google Calendar
加密的 Google Calendar

需求

硬件

任何能够提供 2002 年以后的浏览体验的硬件设备,应该能够运行本文所使用的代码。加密算法属于处理器密集型作业,因此,如果要在一个页面上对十几个,甚至上百个日程表事件进行加密,更快的硬件支持能够提供更好的浏览体验。

软件

需要 Firefox 1.5(或更新的版本)和 GnuPG(Gnu Privacy Guard)。具备各种 Firefox 扩展开发工具也是个不错的主意。查看下面的 参考资料 小节获得这些软件包的链接。

虽然本文的开发基于 Ubuntu 7.10 系统,但所用到的概念适用于各种操作环境。着手开发之前,确保您的系统支持 Perl、GnuPG 和 Firefox。



回页首

主要的编程方法

本文要求您基本熟悉 Firefox 扩展的开发过程。本文不需要特定的编译器和环境配置,但您必须熟悉软件开发,因为可能需要诊断与设置有关的问题或配置错误。

出于开发的需要,建议创建一个新的 Firefox 配置文件。下面的 参考资料 小节提供了如何创建新的配置文件的信息,同时也提供了有用的关于扩展开发站点的链接。

跨平台兼容性
本文的开发基于 Ubuntu 7.10 Linux® 发行版。如果要严格地遵循本文的说明进行操作,必须使用较新的 Linux 发行版。不过这里提到的所有软件(Firefox、GnuPG 和 Perl)都有对应的 Microsoft® Windows® 版本。只要稍微修改路径名和外部程序,这里所用的代码将可以在其他各种平台上使用。

GnuPG 将用于处理这个扩展的所有加密函数。这个方法提高了 JavaScript 的算法实现效率,同时也提供了健壮的跨平台密匙管理。您需要一个功能齐全的 GnuPG 安装,并且能够访问您选择的加密密匙。此外,还需要 gpg-agent 程序(通常是 GnuPG 包的一部分),用于处理已记住的 passphrase 的临时存储和过期。

有了处理加密的函数性扩展开发环境和 GnuPG 之后,可以通过更改前面开发的扩展的一些代码来修改接口。Elias Torres 开发的 Google Quick Add 扩展为插入加密修改提供了一个很好的开端。在加密阶段,当前输入的事件文本将存储在磁盘上,然后一个外部程序对它进行加密,加密后的文件将被读入并发送到 Google 服务器。在解密阶段,每个事件都被写到磁盘上,然后进行解密,最后读回纯文本并显示给用户。另一种代替方法涉及到编写新的协议,该协议由 Firefox 程序管理。尽管这个过程能够提供更加健壮的数据管道,输出、处理和读取则是更加简单的进程间通信方法,适合于这类扩展。

在继续使用本文提供的代码之前,确保您设置了函数性 Firefox GnuPG 和 gpg-agent。



回页首

构建 Google Calendar Quick Add 扩展

Google Calendar Quick Add 扩展利用 Google Calendar SOAP API 从任意页面上获得事件并添加日程表中。本文通过负载截取和过程加密的方式向日程表添加加密事件。一个代替方法是简单地向 Google Calendar 添加经 ASCII 封装的条目,但这里讨论的方法将自动完成这个过程。

首先,创建一个保存扩展目录和代码的目录,比如 mkdir ~/calendarEncrypt。转到这个目录,然后从 参考资料 小节中指定的链接将 Quick Google Calendar Quick Add 扩展 xpi 下载到这个目录。

用以下命令解压缩 xpi:unzip quickgooglecal.xpi。转到刚才创建的 chrome 目录,然后运行命令 unzip quickgooglecal.jar。现在您会看到一个目录树,类似于 清单 1:

清单 1. Google Calendar Quick Add 目录结构

calendarEncrypt/chrome.manifest
calendarEncrypt/readme.txt
calendarEncrypt/chrome
calendarEncrypt/chrome/content
calendarEncrypt/chrome/content/hello.xul
calendarEncrypt/chrome/content/overlay.xul
calendarEncrypt/chrome/content/overlay.js
calendarEncrypt/chrome/skin
calendarEncrypt/chrome/skin/overlay.css
calendarEncrypt/chrome/quickgooglecal.jar
calendarEncrypt/chrome/locale
calendarEncrypt/chrome/locale/en-US
calendarEncrypt/chrome/locale/en-US/hello.dtd
calendarEncrypt/chrome/locale/en-US/overlay.dtd
calendarEncrypt/install.rdf
calendarEncrypt/quickgooglecal.xpi

转到 ~/calendarEncrypt 目录并编辑 install.rdf 文件。更改标识符和创建者,如 清单 2 所示:

清单 2. 更改 install.rdf 的标识符和创建者

Change the identifier:
{E31AE5B1-3E5B-4927-9B48-76C0A701F105}to:
calendarEncrypt@devWorks_IBM.comAlso, change the creator:
Elias Torresto:
Elias Torres with modifications from devWorks

编辑 chrome.manifest 文件,将基于 jar 的扩展打包方式更改为更适合开发人员的目录结构打包方式。清单 3 给出了必要的更改。

清单 3. 将 chrome.manifest jar 更改为目录结构

Original chrome.manifest
content quickgooglecal  jar:chrome/quickgooglecal.jar!/content/
overlay chrome://browser/content/browser.xul chrome://quickgooglecal/content/overlay.xul
locale  quickgooglecal  en-US   jar:chrome/quickgooglecal.jar!/locale/en-US/
skin    quickgooglecal  classic/1.0     jar:chrome/quickgooglecal.jar!/skin/
style   chrome://global/content/customizeToolbar.xul
chrome://quickgooglecal/skin/overlay.css
Change to directory based chrome.manifest:
content quickgooglecal  chrome/content/
overlay chrome://browser/content/browser.xul chrome://quickgooglecal/content/overlay.xul
locale  quickgooglecal  en-US   chrome/locale/en-US/
skin    quickgooglecal  classic/1.0     chrome/skin/
style   chrome://global/content/customizeToolbar.xul
chrome://quickgooglecal/skin/overlay.css

现在,在 Firefox 开发配置文件中为当前的扩展目录设置一个链接。例如,如果您的配置文件是 ~/.mozilla/firefox/b2w2sglj.development,就在 ~/.mozilla/firefox/b2w2sglj.development/extensions/ 创建一个称为 calendarEncrypt@devWorks_IBM.com 的链接。将当前的 Google Quick Add 开发目录路径放置到 calendarEncrypt@devWorks_IBM.com 文件中,在这个示例中是:/home/username/calendarEncrypt

登录 Google Calendar 然后按 ctrl+;激活 Google Calendar Quick Add 扩展。通过输入 Test unencrypted event tomorrow 15:30 验证可以正确添加事件。验证事件可以正确地显示在日程表上。

Google Calendar Quick Add 扩展就绪之后,现在就可以向扩展插入支持加密和解密的修改了。



回页首

修改 Quick Add 扩展,使其支持透明加密

对发送的事件进行加密

本文通过拦截和加密 Quick Add Event 负载的方式,帮助实现自动向日程表添加加密事件。修改现有的扩展使其支持拦截,见下面的 清单 4。编辑 chrome/content/hello.xul 文件并删除第 69 行:var quickAddText = number_html(document.getElementById("quickText").value);。将下面的代码插入到第 69 行:

清单 4. 加密变量,提取日期和时间

var words = document.getElementById("quickText").value.split(' ');
var dayVal = words[words.length-2];
var timeVal = words[words.length-1];
var elementData = "";
for( var n=0; n < words.length-2; n++ ){
elementData = elementData + " " + words[n];
}

以上的代码假设日期和时间通常是快速事件文本中的最后两个单词。为了确保能够在日程表中准确放置,所有后来快速添加的事件的格式必须是 “消息 文本 日期 时间”,日期的格式为 “friday/monday/tomorrow/etc”,时间的格式为 “05:30/10:30/etc”。

将事件描述文本从事件日期和时间分离出来后,接下来应该将事件文本写到磁盘,然后对它进行加密。添加 清单 5 中第 77 行的代码:

清单 5. 将加密文本写到磁盘,然后调用程序

// Write the quick event text to disk
var fileOut = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileOut.initWithPath("/tmp/calendarEvent");
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
foStream.init(fileOut, 0x02 | 0x08 | 0x20, 0666, 0);
foStream.write(elementData, elementData.length);
foStream.close();
// Run the external encryption process
var fileExe = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileExe.initWithPath("/tmp/CalendarCrypt.pl");
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
process.init(fileExe);
var args = ["encrypt"];
process.run(true, args, args.length);

清单 5 的第一部分将配置文件 "/tmp/calendarEvent",将拦截到的事件文本写到磁盘。这个文件包含纯文本数据,但这些数据将在加密完成后被分解并删除。清单 5 的第二部分配置了一个文件,使它能够通过推荐的 nsIProcess 组件执行。/tmp/CalendarCrypt.pl 是一个 Perl 程序(将在后面介绍),它处理加密、解密和安全删除函数。对数据进行加密之后,清单 6 中的代码读回加密后的事件文本。将下面的代码置于 chrome/content/hello.xul 的第 98 行。

清单 6. 读回加密文件,然后构建事件文本

// Read the encrypted text back in
var fileIn = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileIn.initWithPath("/tmp/calendarEvent.asc");
var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
istream.init(fileIn, 0x01, 0444, 0);
istream.QueryInterface(Components.interfaces.nsILineInputStream);
var data = "";
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components./
interfaces.nsIScriptableInputStream);
// above line split on / for formatting, do not include the line break
fstream.init(fileIn, -1, 0, 0);
sstream.init(fstream);
var str = sstream.read(4096);
while (str.length > 0) {
data += str;
str = sstream.read(4096);
}
sstream.close();
fstream.close();
quickAddText = data + "  " + dayVal + " " + timeVal;

清单 5 的代码表示将文本事件写到一个文件中并运行加密程序。清单 6 中的代码表示读入经 ASCII 封装的加密文本文件(附带了存储日期和时间的信息),然后重新启动快速添加文本的过程。

每个通过 Google Calendar Quick Add 程序添加的事件都被拦截、加密,然后以加密的形式发送到 Google 服务器。

为读取的加密事件修改 overlay.js

完成对 chrome/content/hello.xul 中的事件进行加密的代码后,插入这些代码,解密 chrome/content/overlay.js 中的事件。插入 清单 7 中的行,从文件的末尾开始。

清单 7. 解密事件侦听器,组件定义

window.addEventListener("load", function() { calendarDecryptExtension.init(); }, false);
var calendarDecryptExtension = {
init: function() {
var appcontent = document.getElementById("appcontent");   // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", this.onPageLoad, true);
var messagepane = document.getElementById("messagepane"); // mail
if(messagepane)
messagepane.addEventListener("load", function () /
{ calendarDecryptExtension.onPageLoad(); }, false);
// above line split on / for formatting, do not include the line break
},

事件显示页面每次加载时,都会通过 addEventListener 调用 calendarDecryptExtension 函数。calendarDecryptionExtension 函数开始是各种定义和挂钩,保证代码在页面装载时开始运行以及函数可以访问正确的数据。将 清单 8 中的代码置于清单 7 中的代码之下,继续文档解密过程:

清单 8. 写入事件的 onPageLoad 函数

onPageLoad: function(aEvent) {
dump("pre elemenet /n");
var elementData = "NODATA";
var allSpans = content.document.getElementsByTagName("span");
for (var n = 0; n < allSpans.length; n++){
if( allSpans[n].innerHTML.indexOf("BEGIN PGP") > -1 ){
// file output for encrypted event text
elementData = allSpans[n].innerHTML;
var fileOut = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileOut.initWithPath("/tmp/calendarEvent.temp");
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components. /
interfaces.nsIFileOutputStream);
// above line split on / for formatting, do not include the line break
foStream.init(fileOut, 0x02 | 0x08 | 0x20, 0666, 0);
foStream.write(elementData, elementData.length);
foStream.close();

每个日程表条目都存在 HTML 的 span 元素中。临时变量设置完成后,一个 for 循环将处理每个 span 元素,并在文本加密后删除这些内容。下面的 清单 9 调用 CalendarCrypt.pl 程序,使用 "decrypt" 选项,提取事件文本。

清单 9. 运行加密脚本

// create an nsILocalFile for the executable
var fileExe = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileExe.initWithPath("/tmp/CalendarCrypt.pl");
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
process.init(fileExe);
var args = ["decrypt"];
process.run(true, args, args.length);

将加密后的事件文本写到磁盘,然后运行解密程序,这将创建一个不加密的事件文本文件。添加 清单 10 中的代码将数据读回并更改呈现的信息。

清单 10. 读取解密后的文本

// file input for decrypted event text
var data = "";
var fileIn = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileIn.initWithPath("/tmp/calendarEvent.decrypted");
var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
istream.init(fileIn, 0x01, 0444, 0);
istream.QueryInterface(Components.interfaces.nsILineInputStream);
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components. /
interfaces.nsIScriptableInputStream);
// above line split on / for formatting, do not include the line break
fstream.init(fileIn, -1, 0, 0);
sstream.init(fstream);
var str = sstream.read(4096);
while (str.length > 0) {
data += str;
str = sstream.read(4096);
}
sstream.close();
fstream.close();
allSpans[n].innerHTML = data;

从文件读取文本类似于加密阶段中执行的过程。注意,清单 10 中的最后一行,它将当前的 span 数据设置为未加密文本,而不是 “BEGIN PGP...” 原始文本。 添加 清单 11 中的代码,完成解密过程。

清单 11. 分解磁盘上的文本,结束循环

// sanitize the data stored on disk
args = ["shred"];
process.run(true, args, args.length);
}//if the span item is encrypted
}//for each span
}//on page load
}//calendarDecryptExtension

将选项更改为 "shred" 并重用 nsiProcess 组件,确保加密后的事件文本安全地从文件系统的临时位置删除。下一部分给出了前面部分调用的 CalendarCrypt.pl 程序。

CalendarCrypt.pl 程序

为了完成加密支持修改,需要根据下面的清单创建 CalendarCrypt.pl 程序。注意,对于典型 GnuPG 用户,给出的实现假设了一些最可能用到的加密/解密设置。如果愿意,可以考虑用 GnuPG::Encrypt 提供的其他选项替换外部程序调用和设置。例如,如果您希望使用多个键,或者 gpg-agent 与您的配置不兼容,GnuPG::Encrypt Perl 模块提供了许多帮助实现环境兼容的选项。首先,通过 清单 12 中的文本将 Perl 程序 calendarEncrypt.pl 置于 /tmp 中。

清单 12. calendarEncrypt.pl header 和加密

#!/usr/bin/perl -w
# calendarEncrypt.pl - encrypt/decrypt/shred files
use strict;
die "specify a mode " unless @ARGV == 1;
my $mode = $ARGV[0];
chomp(my $userName = `whoami`);
if( $mode eq "encrypt" )
{
my $res = `gpg --yes --armor --encrypt -r $userName /tmp/calendarEvent`;
$res = `shred /tmp/calendarEvent; rm /tmp/calendarEvent`;

在检查选项并设置默认用户名之后,将加密 /tmp/calendarEvent 文件。分解并删除原始的纯文本事件文件,确保磁盘上没有遗留敏感的数据。 下面的 清单 13 描述了解密模式:

清单 13. 文件处理,解密

}elsif( $mode eq "decrypt" )
{
open(INFILE,"/tmp/calendarEvent.temp") or die "no in file";
open(OUTFILE,"> /tmp/calendarEvent.encrypted" ) or die "no file out";
while(my $line =)
{
my $begin = substr( $line, 0, 23 );
print OUTFILE "-----$begin/n";
my $version = substr( $line, 23, 34 );
print OUTFILE "$version/n";
print OUTFILE "/n";
my $body = substr( $line, 57 );
$body = substr($body, 0, length($body)-26);
my @parts = split " ", $body;
for my $piece( @parts )
{
print OUTFILE "$piece/n";
}
print OUTFILE "-----END PGP MESSAGE-----/n";
}#while each line
close(OUTFILE);
close(INFILE);
my $cmd  = qq{ gpg --yes --decrypt /tmp/calendarEvent.encrypted };
$cmd .= qq{ > /tmp/calendarEvent.decrypted };
my $res = `$cmd`;

在上传、处理或显示日程表事件的某个时刻,会丢失一些原始格式。尤其是 “BEGIN PGP” 消息的前缀 “-----” 以及换行指示,将被去除。在调用解密命令之前,清单 13 中的字符串操作函数将替换丢失的格式。最后,清单 14 中的代码负责安全删除解密文本文件,从而去除任何存储在磁盘上的纯文本信息。

清单 14. 分解纯文本文件

}elsif( $mode eq "shred" )
{
my $res = `shred /tmp/calendarEvent.decrypted`;
$res    = `rm /tmp/calendarEvent.decrypted`;
}
# EOF

在将文件保存为 /tmp/CalendarCrypt.pl 之后,要确保该文件在发出以下命令时执行:chmod a+x /tmp/CalendarCrypt.pl



回页首

用例

现在将拦截、加密使用 Google Calendar Quick Add 程序添加的事件,然后以加密的形式将其发布到 Google 服务器。使用 Extension Developer 的 Extension 重新加载所有的 chrome 事件,或重启 Firefox。使用 Ctrl+; 组合键,然后添加一个事件,比如 “Private doctor appointment tomorrow 16:30”。以 “regular” 模式打开您的 Google 日程表,您将看到一个事件描述,类似于 图 1 左边所示的内容。

要显示解密后的事件,请转到链接:http://www.google.com/calendar/htmlembed?src=%40gmail.com,在这里,是您的帐户名,比如 “developer.works” 或 “bob_smith”。页面装载开始时,您会看到将弹出一个 gpg-agent,请求您的 passphrase。为 CalendarCrypt.pl 程序的第 1 小节中 “uname” 命令识别出的用户输入 passphrase,您的事件将被解密并显示在日程表上。



回页首

结束语

使用本文给出的工具和代码,您现在就可以在 Google 日程表中存储加密事件文本。使用 Elias Torres 的 Google Calendar Quick Add Firefox 扩展的修改,对每个添加和查看的事件进行无缝的加密和解密。在重新掌控数据的同时,也获得了 Web 2.0 应用程序带来的好处。

考虑创建一个模糊层(obfuscation layer)来更改事件存储的时间,降低网络流量分析的效率。编写您自己的程序,借助 Google Calendar SOAP API 提取、加密和存储以前和将来的日程表事件。尝试为默认的 Google Calendar 接口创建一个具有 Ajax 风格的透明加密扩展。

from http://www.ibm.com/developerworks/cn/web/wa-googlecal/index.html

转载:通过 Firefox 扩展为 Google Calendar 加密相关推荐

  1. firefox 扩展

    firefox 扩展 <table border="0" cellpadding="4"> <tbody><tr> < ...

  2. 你了解Google Calendar吗?

    Google Calendar Google日历(Google Calendar)Google公司推出的免费在线可共享的日历服务.使用Google Calendar,可以容易地跟踪生活中的重要时间:生 ...

  3. 50个最好的firefox扩展让你尽情冲浪

    50个最好的firefox扩展让你尽情冲浪 Submitted by xyz黑板 on 星期四, 一月 19, 2006 - 19:04 资源 | 开发 xyz黑板翻译 原文:http://pcher ...

  4. Google Calendar 跨平台同步方案(随时同步手机与电脑的日程安排)

    Google Calendar跨平台同步方案 Google 随着搜索引擎的大获成功,不断推出很多受欢迎的服务产品,包括 Gmail,Calendar,Documents,Reader等等.其中的 Go ...

  5. Firefox 扩展开发 install.rdf和chrome.manifest

    现在我们以一个hello world扩展为例来说明Firefox 扩展的基本运行方式.先下载 Hello World extension,解压缩,下面假定路径是c:\helloworld. 设置fir ...

  6. Firefox扩展开发 Hello World!

    今天尝试开发一个Firefox的扩展.虽然比较简单,网上也有很多教程,但是感觉一些教程写的比较麻烦,在初步的开发过程中并没有用到那些东西,于是自己把开发过程记录下来.我是根据Mozilla官方教程开发 ...

  7. firefox扩展开发(二):用XUL创建窗口控件

    firefox扩展开发(二):用XUL创建窗口控件 2008-06-11 16:57 1.创建一个简单的窗口 <?xml version="1.0"?> <?xm ...

  8. Google Calendar API练习

    今天看一篇关于介绍如何在.NET下对Google Calendar API进行操作的文章. Link: http://www.cnblogs.com/SkyD/archive/2009/07/23/1 ...

  9. firefox扩展开发(四) : 更多的窗口控件

    firefox扩展开发(四) : 更多的窗口控件 2008-06-11 17:00 标签盒子 标签盒子是啥?大家都见过,就是分页标签: 对应的代码: <?xml version="1. ...

  10. PHP 文件加密Zend Guard Loader 学习和使用(如何安装ioncube扩展对PHP代码加密)

    一.大体流程图 二.PHP 项目文件加密 下表列出了Zend产品中的PHP版本及其内部API版本和Zend产品版本. 如何加密请往后看 三.如何使用 第一步:确认当前环境 Amai Phalcon 前 ...

最新文章

  1. 体验决定销量,真假4K争论仅仅是忽悠人而已
  2. python 设置x轴_python matplotlib坐标轴设置的方法
  3. GM6 frontend Shell._resolveHashFragment when tile is clicked
  4. 运算符的优先级总能起作用吗?
  5. vs2010上opencv配置
  6. c语言字母等腰三角形边框,用C语言编写一个四行*等腰三角形
  7. endnotex9安装后使用方法_endnotex9使用教程
  8. 基于springboot助学贷款管理毕业设计源码061528
  9. 开发工具总结(8)之图文并茂全面总结上百个AS好用的插件(下)
  10. 爬取去哪儿酒店信息及评论
  11. 王牌特工:黄金圈 经典例句
  12. Sweet 简洁是美
  13. 经常使用网页播放器代码
  14. Android开发之仿360手机卫士悬浮窗效果
  15. 2K分辨率显示器调整缩放125%部分软件模糊的解决办法
  16. 区块链项目ICO注意事项
  17. imagemagick安装问题
  18. Flash鼠绘入门第五课:绘制可口的樱桃-Flash鼠绘可口的樱桃(1)
  19. Unity3D 动态加载本地/网络GLB模型
  20. python中幂运算_python的幂运算

热门文章

  1. 听刘万祥老师讲“模拟范围滑尺的动态图表”
  2. 数据库中的数据模型 概念数据模型、逻辑数据模型、物理数据模型
  3. [NSSCTF][羊城杯2020]WEB复现
  4. Django 4.x Caches 缓存使用示例和配置方法
  5. 傻子的代码怎么写_傻瓜不写单元测试
  6. CAJViewer安装报错卸载报错打开报错解决流程
  7. HTTP请求报文和响应报文、http协议状态码分类和常用状态码、Cookie、curl工具、httpd自带的工具、压力测试工具
  8. 集成AGC崩溃服务如何生成iOS符号文件表并上传
  9. matlab 对信号抽样,matlab信号抽样与恢复
  10. 2022年下半年(软考高级)信息系统项目管理师报名条件